Quelle viewer.mjs
Sprache: unbekannt
|
|
rahmenlose Ansicht.mjs DruckansichtUnknown {[0] [0] [0]} [Methode: Schwerpunktbildung, einfache Gewichte, sechs Dimensionen]
/**
* @licstart The following is the entire license notice for the
* JavaScript code in this page
*
* Copyright 2024 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
*/
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, k ey)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
PDFViewerApplication: () => (/* reexport */ PDFViewerApplication),
PDFViewerApplicationConstants: () => (/* binding */ AppConstants),
PDFViewerApplicationOptions: () => (/* reexport */ AppOptions)
});
;// ./web/ui_utils.js
const DEFAULT_SCALE_VALUE = "auto";
const DEFAULT_SCALE = 1.0;
const DEFAULT_SCALE_DELTA = 1.1;
const MIN_SCALE = 0.1;
const MAX_SCALE = 10.0;
const UNKNOWN_SCALE = 0;
const MAX_AUTO_SCALE = 1.25;
const SCROLLBAR_PADDING = 40;
const VERTICAL_PADDING = 5;
const RenderingStates = {
INITIAL: 0,
RUNNING: 1,
PAUSED: 2,
FINISHED: 3
};
const PresentationModeState = {
UNKNOWN: 0,
NORMAL: 1,
CHANGING: 2,
FULLSCREEN: 3
};
const SidebarView = {
UNKNOWN: -1,
NONE: 0,
THUMBS: 1,
OUTLINE: 2,
ATTACHMENTS: 3,
LAYERS: 4
};
const TextLayerMode = {
DISABLE: 0,
ENABLE: 1,
ENABLE_PERMISSIONS: 2
};
const ScrollMode = {
UNKNOWN: -1,
VERTICAL: 0,
HORIZONTAL: 1,
WRAPPED: 2,
PAGE: 3
};
const SpreadMode = {
UNKNOWN: -1,
NONE: 0,
ODD: 1,
EVEN: 2
};
const CursorTool = {
SELECT: 0,
HAND: 1,
ZOOM: 2
};
const AutoPrintRegExp = /\bprint\s*\(/;
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, abortSignal = undefined) {
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, {
useCapture: true,
signal: abortSignal
});
abortSignal?.addEventListener("abort", () => window.cancelAnimationFrame(rAF), {
once: 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 InvisibleCharsRegExp = /[\x00-\x1F]/g;
function removeNullCharacters(str, replaceInvisible = false) {
if (!InvisibleCharsRegExp.test(str)) {
return str;
}
if (replaceInvisible) {
return str.replaceAll(InvisibleCharsRegExp, m => m === "\x00" ? "" : " ");
}
return str.replaceAll("\x00", "");
}
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 floorToDivide(x, div) {
return x - x % 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 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) {
const deltaMode = evt.deltaMode;
let delta = normalizeWheelEventDirection(evt);
const MOUSE_PIXELS_PER_LINE = 30;
const MOUSE_LINES_PER_PAGE = 30;
if (deltaMode === WheelEvent.DOM_DELTA_PIXEL) {
delta /= MOUSE_PIXELS_PER_LINE * MOUSE_LINES_PER_PAGE;
} else if (deltaMode === WheelEvent.DOM_DELTA_LINE) {
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) {
window.requestAnimationFrame(resolve);
});
const docStyle = document.documentElement.style;
function clamp(v, min, max) {
return Math.min(Math.max(v, min), max);
}
class ProgressBar {
#classList = null;
#disableAutoFetchTimeout = null;
#percent = 0;
#style = null;
#visible = true;
constructor(bar) {
this.#classList = bar.classList;
this.#style = bar.style;
}
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");
this.#style.setProperty("--progressBar-percent", `${this.#percent}%`);
}
setWidth(viewer) {
if (!viewer) {
return;
}
const container = viewer.parentNode;
const scrollbarWidth = container.offsetWidth - viewer.offsetWidth;
if (scrollbarWidth > 0) {
this.#style.setProperty("--progressBar-end-offset", `${scrollbarWidth}px`);
}
}
setDisableAutoFetch(delay = 5000) {
if (this.#percent === 100 || isNaN(this.#percent)) {
return;
}
if (this.#disableAutoFetchTimeout) {
clearTimeout(this.#disableAutoFetchTimeout);
}
this.show();
this.#disableAutoFetchTimeout = setTimeout(() => {
this.#disableAutoFetchTimeout = null;
this.hide();
}, delay);
}
hide() {
if (!this.#visible) {
return;
}
this.#visible = false;
this.#classList.add("hidden");
}
show() {
if (this.#visible) {
return;
}
this.#visible = true;
this.#classList.remove("hidden");
}
}
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;
}
function toggleCheckedBtn(button, toggle, view = null) {
button.classList.toggle("toggled", toggle);
button.setAttribute("aria-checked", toggle);
view?.classList.toggle("hidden", !toggle);
}
function toggleExpandedBtn(button, toggle, view = null) {
button.classList.toggle("toggled", toggle);
button.setAttribute("aria-expanded", toggle);
view?.classList.toggle("hidden", !toggle);
}
const calcRound = Math.fround;
;// ./web/app_options.js
const OptionKind = {
BROWSER: 0x01,
VIEWER: 0x02,
API: 0x04,
WORKER: 0x08,
EVENT_DISPATCH: 0x10,
PREFERENCE: 0x80
};
const Type = {
BOOLEAN: 0x01,
NUMBER: 0x02,
OBJECT: 0x04,
STRING: 0x08,
UNDEFINED: 0x10
};
const defaultOptions = {
allowedGlobalEvents: {
value: null,
kind: OptionKind.BROWSER
},
canvasMaxAreaInBytes: {
value: -1,
kind: OptionKind.BROWSER + OptionKind.API
},
isInAutomation: {
value: false,
kind: OptionKind.BROWSER
},
localeProperties: {
value: null,
kind: OptionKind.BROWSER
},
nimbusDataStr: {
value: "",
kind: OptionKind.BROWSER
},
supportsCaretBrowsingMode: {
value: false,
kind: OptionKind.BROWSER
},
supportsDocumentFonts: {
value: true,
kind: OptionKind.BROWSER
},
supportsIntegratedFind: {
value: false,
kind: OptionKind.BROWSER
},
supportsMouseWheelZoomCtrlKey: {
value: true,
kind: OptionKind.BROWSER
},
supportsMouseWheelZoomMetaKey: {
value: true,
kind: OptionKind.BROWSER
},
supportsPinchToZoom: {
value: true,
kind: OptionKind.BROWSER
},
toolbarDensity: {
value: 0,
kind: OptionKind.BROWSER + OptionKind.EVENT_DISPATCH
},
altTextLearnMoreUrl: {
value: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pdf-alt-text",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
annotationEditorMode: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
annotationMode: {
value: 2,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
cursorToolOnLoad: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
debuggerSrc: {
value: "./debugger.mjs",
kind: OptionKind.VIEWER
},
defaultZoomDelay: {
value: 400,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
defaultZoomValue: {
value: "",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
disableHistory: {
value: false,
kind: OptionKind.VIEWER
},
disablePageLabels: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableAltText: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableAltTextModelDownload: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH
},
enableGuessAltText: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH
},
enableHighlightFloatingButton: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableNewAltTextWhenAddingImage: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enablePermissions: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enablePrintAutoRotate: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableScripting: {
value: true,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableUpdatedAddImage: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
externalLinkRel: {
value: "noopener noreferrer nofollow",
kind: OptionKind.VIEWER
},
externalLinkTarget: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
highlightEditorColors: {
value: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
historyUpdateUrl: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
ignoreDestinationZoom: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
imageResourcesPath: {
value: "resource://pdf.js/web/images/",
kind: OptionKind.VIEWER
},
maxCanvasPixels: {
value: 2 ** 25,
kind: OptionKind.VIEWER
},
forcePageColors: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
pageColorsBackground: {
value: "Canvas",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
pageColorsForeground: {
value: "CanvasText",
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
pdfBugEnabled: {
value: false,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
printResolution: {
value: 150,
kind: OptionKind.VIEWER
},
sidebarViewOnLoad: {
value: -1,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
scrollModeOnLoad: {
value: -1,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
spreadModeOnLoad: {
value: -1,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
textLayerMode: {
value: 1,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
viewOnLoad: {
value: 0,
kind: OptionKind.VIEWER + OptionKind.PREFERENCE
},
cMapPacked: {
value: true,
kind: OptionKind.API
},
cMapUrl: {
value: "resource://pdf.js/web/cmaps/",
kind: OptionKind.API
},
disableAutoFetch: {
value: false,
kind: OptionKind.API + OptionKind.PREFERENCE
},
disableFontFace: {
value: false,
kind: OptionKind.API + OptionKind.PREFERENCE
},
disableRange: {
value: false,
kind: OptionKind.API + OptionKind.PREFERENCE
},
disableStream: {
value: false,
kind: OptionKind.API + OptionKind.PREFERENCE
},
docBaseUrl: {
value: "",
kind: OptionKind.API
},
enableHWA: {
value: false,
kind: OptionKind.API + OptionKind.VIEWER + OptionKind.PREFERENCE
},
enableXfa: {
value: true,
kind: OptionKind.API + OptionKind.PREFERENCE
},
fontExtraProperties: {
value: false,
kind: OptionKind.API
},
isEvalSupported: {
value: true,
kind: OptionKind.API
},
isOffscreenCanvasSupported: {
value: true,
kind: OptionKind.API
},
maxImageSize: {
value: -1,
kind: OptionKind.API
},
pdfBug: {
value: false,
kind: OptionKind.API
},
standardFontDataUrl: {
value: "resource://pdf.js/web/standard_fonts/",
kind: OptionKind.API
},
useSystemFonts: {
value: undefined,
kind: OptionKind.API,
type: Type.BOOLEAN + Type.UNDEFINED
},
verbosity: {
value: 1,
kind: OptionKind.API
},
wasmUrl: {
value: "resource://pdf.js/web/wasm/",
kind: OptionKind.API
},
workerPort: {
value: null,
kind: OptionKind.WORKER
},
workerSrc: {
value: "resource://pdf.js/build/pdf.worker.mjs",
kind: OptionKind.WORKER
}
};
class AppOptions {
static eventBus;
static #opts = new Map();
static {
for (const name in defaultOptions) {
this.#opts.set(name, defaultOptions[name].value);
}
}
static get(name) {
return this.#opts.get(name);
}
static getAll(kind = null, defaultOnly = false) {
const options = Object.create(null);
for (const name in defaultOptions) {
const defaultOpt = defaultOptions[name];
if (kind && !(kind & defaultOpt.kind)) {
continue;
}
options[name] = !defaultOnly ? this.#opts.get(name) : defaultOpt.value;
}
return options;
}
static set(name, value) {
this.setAll({
[name]: value
});
}
static setAll(options, prefs = false) {
let events;
for (const name in options) {
const defaultOpt = defaultOptions[name],
userOpt = options[name];
if (!defaultOpt || !(typeof userOpt === typeof defaultOpt.value || Type[(typeof userOpt).toUpperCase()] & defaultOpt.type)) {
continue;
}
const {
kind
} = defaultOpt;
if (prefs && !(kind & OptionKind.BROWSER || kind & OptionKind.PREFERENCE)) {
continue;
}
if (this.eventBus && kind & OptionKind.EVENT_DISPATCH) {
(events ||= new Map()).set(name, userOpt);
}
this.#opts.set(name, userOpt);
}
if (events) {
for (const [name, value] of events) {
this.eventBus.dispatch(name.toLowerCase(), {
source: this,
value
});
}
}
}
}
;// ./web/pdf_link_service.js
const DEFAULT_LINK_REL = "noopener noreferrer nofollow";
const LinkTarget = {
NONE: 0,
SELF: 1,
BLANK: 2,
PARENT: 3,
TOP: 4
};
class PDFLinkService {
externalLinkEnabled = true;
constructor({
eventBus,
externalLinkTarget = null,
externalLinkRel = null,
ignoreDestinationZoom = false
} = {}) {
this.eventBus = eventBus;
this.externalLinkTarget = externalLinkTarget;
this.externalLinkRel = externalLinkRel;
this._ignoreDestinationZoom = ignoreDestinationZoom;
this.baseUrl = null;
this.pdfDocument = null;
this.pdfViewer = null;
this.pdfHistory = null;
}
setDocument(pdfDocument, baseUrl = null) {
this.baseUrl = baseUrl;
this.pdfDocument = pdfDocument;
}
setViewer(pdfViewer) {
this.pdfViewer = pdfViewer;
}
setHistory(pdfHistory) {
this.pdfHistory = pdfHistory;
}
get pagesCount() {
return this.pdfDocument ? this.pdfDocument.numPages : 0;
}
get page() {
return this.pdfDocument ? this.pdfViewer.currentPageNumber : 1;
}
set page(value) {
if (this.pdfDocument) {
this.pdfViewer.currentPageNumber = value;
}
}
get rotation() {
return this.pdfDocument ? this.pdfViewer.pagesRotation : 0;
}
set rotation(value) {
if (this.pdfDocument) {
this.pdfViewer.pagesRotation = value;
}
}
get isInPresentationMode() {
return this.pdfDocument ? this.pdfViewer.isInPresentationMode : false;
}
async goToDestination(dest) {
if (!this.pdfDocument) {
return;
}
let namedDest, explicitDest, pageNumber;
if (typeof dest === "string") {
namedDest = dest;
explicitDest = await this.pdfDocument.getDestination(dest);
} else {
namedDest = null;
explicitDest = await dest;
}
if (!Array.isArray(explicitDest)) {
console.error(`goToDestination: "${explicitDest}" is not a valid destination array, for dest="${dest}".`);
return;
}
const [destRef] = explicitDest;
if (destRef && typeof destRef === "object") {
pageNumber = this.pdfDocument.cachedPageNumber(destRef);
if (!pageNumber) {
try {
pageNumber = (await this.pdfDocument.getPageIndex(destRef)) + 1;
} catch {
console.error(`goToDestination: "${destRef}" is not a valid page reference, for dest="${dest}".`);
return;
}
}
} else if (Number.isInteger(destRef)) {
pageNumber = destRef + 1;
}
if (!pageNumber || pageNumber < 1 || pageNumber > this.pagesCount) {
console.error(`goToDestination: "${pageNumber}" is not a valid page number, for dest="${dest}".`);
return;
}
if (this.pdfHistory) {
this.pdfHistory.pushCurrentPosition();
this.pdfHistory.push({
namedDest,
explicitDest,
pageNumber
});
}
this.pdfViewer.scrollPageIntoView({
pageNumber,
destArray: explicitDest,
ignoreDestinationZoom: this._ignoreDestinationZoom
});
}
goToPage(val) {
if (!this.pdfDocument) {
return;
}
const pageNumber = typeof val === "string" && this.pdfViewer.pageLabelToPageNumber(val) || val | 0;
if (!(Number.isInteger(pageNumber) && pageNumber > 0 && pageNumber <= this.pagesCount)) {
console.error(`PDFLinkService.goToPage: "${val}" is not a valid page.`);
return;
}
if (this.pdfHistory) {
this.pdfHistory.pushCurrentPosition();
this.pdfHistory.pushPage(pageNumber);
}
this.pdfViewer.scrollPageIntoView({
pageNumber
});
}
addLinkAttributes(link, url, newWindow = false) {
if (!url || typeof url !== "string") {
throw new Error('A valid "url" parameter must provided.');
}
const target = newWindow ? LinkTarget.BLANK : this.externalLinkTarget,
rel = this.externalLinkRel;
if (this.externalLinkEnabled) {
link.href = link.title = url;
} else {
link.href = "";
link.title = `Disabled: ${url}`;
link.onclick = () => false;
}
let targetStr = "";
switch (target) {
case LinkTarget.NONE:
break;
case LinkTarget.SELF:
targetStr = "_self";
break;
case LinkTarget.BLANK:
targetStr = "_blank";
break;
case LinkTarget.PARENT:
targetStr = "_parent";
break;
case LinkTarget.TOP:
targetStr = "_top";
break;
}
link.target = targetStr;
link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
}
getDestinationHash(dest) {
if (typeof dest === "string") {
if (dest.length > 0) {
return this.getAnchorUrl("#" + escape(dest));
}
} else if (Array.isArray(dest)) {
const str = JSON.stringify(dest);
if (str.length > 0) {
return this.getAnchorUrl("#" + escape(str));
}
}
return this.getAnchorUrl("");
}
getAnchorUrl(anchor) {
return this.baseUrl ? this.baseUrl + anchor : anchor;
}
setHash(hash) {
if (!this.pdfDocument) {
return;
}
let pageNumber, dest;
if (hash.includes("=")) {
const params = parseQueryString(hash);
if (params.has("search")) {
const query = params.get("search").replaceAll('"', ""),
phrase = params.get("phrase") === "true";
this.eventBus.dispatch("findfromurlhash", {
source: this,
query: phrase ? query : query.match(/\S+/g)
});
}
if (params.has("page")) {
pageNumber = params.get("page") | 0 || 1;
}
if (params.has("zoom")) {
const zoomArgs = params.get("zoom").split(",");
const zoomArg = zoomArgs[0];
const zoomArgNumber = parseFloat(zoomArg);
if (!zoomArg.includes("Fit")) {
dest = [null, {
name: "XYZ"
}, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null, zoomArgs.length > 2 ? zoomArgs[2] | 0 : null, zoomArgNumber ? zoomArgNumber / 100 : zoomArg];
} else if (zoomArg === "Fit" || zoomArg === "FitB") {
dest = [null, {
name: zoomArg
}];
} else if (zoomArg === "FitH" || zoomArg === "FitBH" || zoomArg === "FitV" || zoomArg === "FitBV") {
dest = [null, {
name: zoomArg
}, zoomArgs.length > 1 ? zoomArgs[1] | 0 : null];
} else if (zoomArg === "FitR") {
if (zoomArgs.length !== 5) {
console.error('PDFLinkService.setHash: Not enough parameters for "FitR".');
} else {
dest = [null, {
name: zoomArg
}, zoomArgs[1] | 0, zoomArgs[2] | 0, zoomArgs[3] | 0, zoomArgs[4] | 0];
}
} else {
console.error(`PDFLinkService.setHash: "${zoomArg}" is not a valid zoom value.`);
}
}
if (dest) {
this.pdfViewer.scrollPageIntoView({
pageNumber: pageNumber || this.page,
destArray: dest,
allowNegativeOffset: true
});
} else if (pageNumber) {
this.page = pageNumber;
}
if (params.has("pagemode")) {
this.eventBus.dispatch("pagemode", {
source: this,
mode: params.get("pagemode")
});
}
if (params.has("nameddest")) {
this.goToDestination(params.get("nameddest"));
}
if (!params.has("filename") || !params.has("filedest")) {
return;
}
hash = params.get("filedest");
}
dest = unescape(hash);
try {
dest = JSON.parse(dest);
if (!Array.isArray(dest)) {
dest = dest.toString();
}
} catch {}
if (typeof dest === "string" || PDFLinkService.#isValidExplicitDest(dest)) {
this.goToDestination(dest);
return;
}
console.error(`PDFLinkService.setHash: "${unescape(hash)}" is not a valid destination.`);
}
executeNamedAction(action) {
if (!this.pdfDocument) {
return;
}
switch (action) {
case "GoBack":
this.pdfHistory?.back();
break;
case "GoForward":
this.pdfHistory?.forward();
break;
case "NextPage":
this.pdfViewer.nextPage();
break;
case "PrevPage":
this.pdfViewer.previousPage();
break;
case "LastPage":
this.page = this.pagesCount;
break;
case "FirstPage":
this.page = 1;
break;
default:
break;
}
this.eventBus.dispatch("namedaction", {
source: this,
action
});
}
async executeSetOCGState(action) {
if (!this.pdfDocument) {
return;
}
const pdfDocument = this.pdfDocument,
optionalContentConfig = await this.pdfViewer.optionalContentConfigPromise;
if (pdfDocument !== this.pdfDocument) {
return;
}
optionalContentConfig.setOCGState(action);
this.pdfViewer.optionalContentConfigPromise = Promise.resolve(optionalContentConfig);
}
static #isValidExplicitDest(dest) {
if (!Array.isArray(dest) || dest.length < 2) {
return false;
}
const [page, zoom, ...args] = dest;
if (!(typeof page === "object" && Number.isInteger(page?.num) && Number.isInteger(page?.gen)) && !Number.isInteger(page)) {
return false;
}
if (!(typeof zoom === "object" && typeof zoom?.name === "string")) {
return false;
}
const argsLen = args.length;
let allowNull = true;
switch (zoom.name) {
case "XYZ":
if (argsLen < 2 || argsLen > 3) {
return false;
}
break;
case "Fit":
case "FitB":
return argsLen === 0;
case "FitH":
case "FitBH":
case "FitV":
case "FitBV":
if (argsLen > 1) {
return false;
}
break;
case "FitR":
if (argsLen !== 4) {
return false;
}
allowNull = false;
break;
default:
return false;
}
for (const arg of args) {
if (!(typeof arg === "number" || allowNull && arg === null)) {
return false;
}
}
return true;
}
}
class SimpleLinkService extends PDFLinkService {
setDocument(pdfDocument, baseUrl = null) {}
}
;// ./web/pdfjs.js
const {
AbortException,
AnnotationEditorLayer,
AnnotationEditorParamsType,
AnnotationEditorType,
AnnotationEditorUIManager,
AnnotationLayer,
AnnotationMode,
build,
ColorPicker,
createValidAbsoluteUrl,
DOMSVGFactory,
DrawLayer,
FeatureTest,
fetchData,
getDocument,
getFilenameFromUrl,
getPdfFilenameFromUrl,
getXfaPageViewport,
GlobalWorkerOptions,
ImageKind,
InvalidPDFException,
isDataScheme,
isPdfFile,
noContextMenu,
normalizeUnicode,
OPS,
OutputScale,
PasswordResponses,
PDFDataRangeTransport,
PDFDateString,
PDFWorker,
PermissionFlag,
PixelsPerInch,
RenderingCancelledException,
ResponseException,
setLayerDimensions,
shadow,
stopEvent,
TextLayer,
TouchManager,
Util,
VerbosityLevel,
version,
XfaLayer
} = globalThis.pdfjsLib;
;// ./web/event_utils.js
const WaitOnType = {
EVENT: "event",
TIMEOUT: "timeout"
};
async function waitOnEventOrTimeout({
target,
name,
delay = 0
}) {
if (typeof target !== "object" || !(name && typeof name === "string") || !(Number.isInteger(delay) && delay >= 0)) {
throw new Error("waitOnEventOrTimeout - invalid parameters.");
}
const {
promise,
resolve
} = Promise.withResolvers();
const ac = new AbortController();
function handler(type) {
ac.abort();
clearTimeout(timeout);
resolve(type);
}
const evtMethod = target instanceof EventBus ? "_on" : "addEventListener";
target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), {
signal: ac.signal
});
const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay);
return promise;
}
class EventBus {
#listeners = Object.create(null);
on(eventName, listener, options = null) {
this._on(eventName, listener, {
external: true,
once: options?.once,
signal: options?.signal
});
}
off(eventName, listener, options = null) {
this._off(eventName, listener);
}
dispatch(eventName, data) {
const eventListeners = this.#listeners[eventName];
if (!eventListeners || eventListeners.length === 0) {
return;
}
let externalListeners;
for (const {
listener,
external,
once
} of eventListeners.slice(0)) {
if (once) {
this._off(eventName, listener);
}
if (external) {
(externalListeners ||= []).push(listener);
continue;
}
listener(data);
}
if (externalListeners) {
for (const listener of externalListeners) {
listener(data);
}
externalListeners = null;
}
}
_on(eventName, listener, options = null) {
let rmAbort = null;
if (options?.signal instanceof AbortSignal) {
const {
signal
} = options;
if (signal.aborted) {
console.error("Cannot use an `aborted` signal.");
return;
}
const onAbort = () => this._off(eventName, listener);
rmAbort = () => signal.removeEventListener("abort", onAbort);
signal.addEventListener("abort", onAbort);
}
const eventListeners = this.#listeners[eventName] ||= [];
eventListeners.push({
listener,
external: options?.external === true,
once: options?.once === true,
rmAbort
});
}
_off(eventName, listener, options = null) {
const eventListeners = this.#listeners[eventName];
if (!eventListeners) {
return;
}
for (let i = 0, ii = eventListeners.length; i < ii; i++) {
const evt = eventListeners[i];
if (evt.listener === listener) {
evt.rmAbort?.();
eventListeners.splice(i, 1);
return;
}
}
}
}
class FirefoxEventBus extends EventBus {
#externalServices;
#globalEventNames;
#isInAutomation;
constructor(globalEventNames, externalServices, isInAutomation) {
super();
this.#globalEventNames = globalEventNames;
this.#externalServices = externalServices;
this.#isInAutomation = isInAutomation;
}
dispatch(eventName, data) {
super.dispatch(eventName, data);
if (this.#isInAutomation) {
const detail = Object.create(null);
if (data) {
for (const key in data) {
const value = data[key];
if (key === "source") {
if (value === window || value === document) {
return;
}
continue;
}
detail[key] = value;
}
}
const event = new CustomEvent(eventName, {
bubbles: true,
cancelable: true,
detail
});
document.dispatchEvent(event);
}
if (this.#globalEventNames?.has(eventName)) {
this.#externalServices.dispatchGlobalEvent({
eventName,
detail: data
});
}
}
}
;// ./web/external_services.js
class BaseExternalServices {
updateFindControlState(data) {}
updateFindMatchesCount(data) {}
initPassiveLoading() {}
reportTelemetry(data) {}
async createL10n() {
throw new Error("Not implemented: createL10n");
}
createScripting() {
throw new Error("Not implemented: createScripting");
}
updateEditorStates(data) {
throw new Error("Not implemented: updateEditorStates");
}
dispatchGlobalEvent(_event) {}
}
;// ./web/preferences.js
class BasePreferences {
#defaults = Object.freeze({
altTextLearnMoreUrl: "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/pdf-alt-text",
annotationEditorMode: 0,
annotationMode: 2,
cursorToolOnLoad: 0,
defaultZoomDelay: 400,
defaultZoomValue: "",
disablePageLabels: false,
enableAltText: false,
enableAltTextModelDownload: true,
enableGuessAltText: true,
enableHighlightFloatingButton: false,
enableNewAltTextWhenAddingImage: true,
enablePermissions: false,
enablePrintAutoRotate: true,
enableScripting: true,
enableUpdatedAddImage: false,
externalLinkTarget: 0,
highlightEditorColors: "yellow=#FFFF98,green=#53FFBC,blue=#80EBFF,pink=#FFCBE6,red=#FF4F5F",
historyUpdateUrl: false,
ignoreDestinationZoom: false,
forcePageColors: false,
pageColorsBackground: "Canvas",
pageColorsForeground: "CanvasText",
pdfBugEnabled: false,
sidebarViewOnLoad: -1,
scrollModeOnLoad: -1,
spreadModeOnLoad: -1,
textLayerMode: 1,
viewOnLoad: 0,
disableAutoFetch: false,
disableFontFace: false,
disableRange: false,
disableStream: false,
enableHWA: false,
enableXfa: true
});
#initializedPromise = null;
constructor() {
this.#initializedPromise = this._readFromStorage(this.#defaults).then(({
browserPrefs,
prefs
}) => {
AppOptions.setAll({
...browserPrefs,
...prefs
}, true);
});
window.addEventListener("updatedPreference", async ({
detail: {
name,
value
}
}) => {
await this.#initializedPromise;
AppOptions.setAll({
[name]: value
}, true);
});
}
async _writeToStorage(prefObj) {
throw new Error("Not implemented: _writeToStorage");
}
async _readFromStorage(prefObj) {
throw new Error("Not implemented: _readFromStorage");
}
async reset() {
throw new Error("Please use `about:config` to change preferences.");
}
async set(name, value) {
await this.#initializedPromise;
AppOptions.setAll({
[name]: value
}, true);
await this._writeToStorage({
[name]: AppOptions.get(name)
});
}
async get(name) {
throw new Error("Not implemented: get");
}
get initializedPromise() {
return this.#initializedPromise;
}
}
;// ./web/l10n.js
class L10n {
#dir;
#elements;
#lang;
#l10n;
constructor({
lang,
isRTL
}, l10n = null) {
this.#lang = L10n.#fixupLangCode(lang);
this.#l10n = l10n;
this.#dir = isRTL ?? L10n.#isRTL(this.#lang) ? "rtl" : "ltr";
}
_setL10n(l10n) {
this.#l10n = l10n;
}
getLanguage() {
return this.#lang;
}
getDirection() {
return this.#dir;
}
async get(ids, args = null, fallback) {
if (Array.isArray(ids)) {
ids = ids.map(id => ({
id
}));
const messages = await this.#l10n.formatMessages(ids);
return messages.map(message => message.value);
}
const messages = await this.#l10n.formatMessages([{
id: ids,
args
}]);
return messages[0]?.value || fallback;
}
async translate(element) {
(this.#elements ||= new Set()).add(element);
try {
this.#l10n.connectRoot(element);
await this.#l10n.translateRoots();
} catch {}
}
async translateOnce(element) {
try {
await this.#l10n.translateElements([element]);
} catch (ex) {
console.error("translateOnce:", ex);
}
}
async destroy() {
if (this.#elements) {
for (const element of this.#elements) {
this.#l10n.disconnectRoot(element);
}
this.#elements.clear();
this.#elements = null;
}
this.#l10n.pauseObserving();
}
pause() {
this.#l10n.pauseObserving();
}
resume() {
this.#l10n.resumeObserving();
}
static #fixupLangCode(langCode) {
langCode = langCode?.toLowerCase() || "en-us";
const PARTIAL_LANG_CODES = {
en: "en-us",
es: "es-es",
fy: "fy-nl",
ga: "ga-ie",
gu: "gu-in",
hi: "hi-in",
hy: "hy-am",
nb: "nb-no",
ne: "ne-np",
nn: "nn-no",
pa: "pa-in",
pt: "pt-pt",
sv: "sv-se",
zh: "zh-cn"
};
return PARTIAL_LANG_CODES[langCode] || langCode;
}
static #isRTL(lang) {
const shortCode = lang.split("-", 1)[0];
return ["ar", "he", "fa", "ps", "ur"].includes(shortCode);
}
}
const GenericL10n = null;
;// ./web/firefoxcom.js
let viewerApp = {
initialized: false
};
function initCom(app) {
viewerApp = app;
}
class FirefoxCom {
static requestAsync(action, data) {
return new Promise(resolve => {
this.request(action, data, resolve);
});
}
static request(action, data, callback = null) {
const request = document.createTextNode("");
if (callback) {
request.addEventListener("pdf.js.response", event => {
const response = event.detail.response;
event.target.remove();
callback(response);
}, {
once: true
});
}
document.documentElement.append(request);
const sender = new CustomEvent("pdf.js.message", {
bubbles: true,
cancelable: false,
detail: {
action,
data,
responseExpected: !!callback
}
});
request.dispatchEvent(sender);
}
}
class DownloadManager {
#openBlobUrls = new WeakMap();
downloadData(data, filename, contentType) {
const blobUrl = URL.createObjectURL(new Blob([data], {
type: contentType
}));
FirefoxCom.request("download", {
blobUrl,
originalUrl: blobUrl,
filename,
isAttachment: true
});
}
openOrDownloadData(data, filename, dest = null) {
const isPdfData = isPdfFile(filename);
const contentType = isPdfData ? "application/pdf" : "";
if (isPdfData) {
let blobUrl = this.#openBlobUrls.get(data);
if (!blobUrl) {
blobUrl = URL.createObjectURL(new Blob([data], {
type: contentType
}));
this.#openBlobUrls.set(data, blobUrl);
}
let viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename);
if (dest) {
viewerUrl += `&filedest=${escape(dest)}`;
}
try {
window.open(viewerUrl);
return true;
} catch (ex) {
console.error("openOrDownloadData:", ex);
URL.revokeObjectURL(blobUrl);
this.#openBlobUrls.delete(data);
}
}
this.downloadData(data, filename, contentType);
return false;
}
download(data, url, filename) {
const blobUrl = data ? URL.createObjectURL(new Blob([data], {
type: "application/pdf"
})) : null;
FirefoxCom.request("download", {
blobUrl,
originalUrl: url,
filename
});
}
}
class Preferences extends BasePreferences {
async _readFromStorage(prefObj) {
return FirefoxCom.requestAsync("getPreferences", prefObj);
}
async _writeToStorage(prefObj) {
return FirefoxCom.requestAsync("setPreferences", prefObj);
}
}
(function listenFindEvents() {
const events = ["find", "findagain", "findhighlightallchange", "findcasesensitivitychange", "findentirewordchange", "findbarclose", "finddiacriticmatchingchange"];
const findLen = "find".length;
const handleEvent = function ({
type,
detail
}) {
if (!viewerApp.initialized) {
return;
}
if (type === "findbarclose") {
viewerApp.eventBus.dispatch(type, {
source: window
});
return;
}
viewerApp.eventBus.dispatch("find", {
source: window,
type: type.substring(findLen),
query: detail.query,
caseSensitive: !!detail.caseSensitive,
entireWord: !!detail.entireWord,
highlightAll: !!detail.highlightAll,
findPrevious: !!detail.findPrevious,
matchDiacritics: !!detail.matchDiacritics
});
};
for (const event of events) {
window.addEventListener(event, handleEvent);
}
})();
(function listenZoomEvents() {
const events = ["zoomin", "zoomout", "zoomreset"];
const handleEvent = function ({
type,
detail
}) {
if (!viewerApp.initialized) {
return;
}
if (type === "zoomreset" && viewerApp.pdfViewer.currentScaleValue === DEFAULT_SCALE_VALUE) {
return;
}
viewerApp.eventBus.dispatch(type, {
source: window
});
};
for (const event of events) {
window.addEventListener(event, handleEvent);
}
})();
(function listenSaveEvent() {
const handleEvent = function ({
type,
detail
}) {
if (!viewerApp.initialized) {
return;
}
viewerApp.eventBus.dispatch("download", {
source: window
});
};
window.addEventListener("save", handleEvent);
})();
(function listenEditingEvent() {
const handleEvent = function ({
detail
}) {
if (!viewerApp.initialized) {
return;
}
viewerApp.eventBus.dispatch("editingaction", {
source: window,
name: detail.name
});
};
window.addEventListener("editingaction", handleEvent);
})();
class FirefoxComDataRangeTransport extends PDFDataRangeTransport {
requestDataRange(begin, end) {
FirefoxCom.request("requestDataRange", {
begin,
end
});
}
abort() {
FirefoxCom.request("abortLoading", null);
}
}
class FirefoxScripting {
static async createSandbox(data) {
const success = await FirefoxCom.requestAsync("createSandbox", data);
if (!success) {
throw new Error("Cannot create sandbox.");
}
}
static async dispatchEventInSandbox(event) {
FirefoxCom.request("dispatchEventInSandbox", event);
}
static async destroySandbox() {
FirefoxCom.request("destroySandbox", null);
}
}
class MLManager {
#abortSignal = null;
#enabled = null;
#eventBus = null;
#ready = null;
#requestResolvers = null;
hasProgress = false;
static #AI_ALT_TEXT_MODEL_NAME = "moz-image-to-text";
constructor({
altTextLearnMoreUrl,
enableGuessAltText,
enableAltTextModelDownload
}) {
this.altTextLearnMoreUrl = altTextLearnMoreUrl;
this.enableAltTextModelDownload = enableAltTextModelDownload;
this.enableGuessAltText = enableGuessAltText;
}
setEventBus(eventBus, abortSignal) {
this.#eventBus = eventBus;
this.#abortSignal = abortSignal;
eventBus._on("enablealttextmodeldownload", ({
value
}) => {
if (this.enableAltTextModelDownload === value) {
return;
}
if (value) {
this.downloadModel("altText");
} else {
this.deleteModel("altText");
}
}, {
signal: abortSignal
});
eventBus._on("enableguessalttext", ({
value
}) => {
this.toggleService("altText", value);
}, {
signal: abortSignal
});
}
async isEnabledFor(name) {
return this.enableGuessAltText && !!(await this.#enabled?.get(name));
}
isReady(name) {
return this.#ready?.has(name) ?? false;
}
async deleteModel(name) {
if (name !== "altText" || !this.enableAltTextModelDownload) {
return;
}
this.enableAltTextModelDownload = false;
this.#ready?.delete(name);
this.#enabled?.delete(name);
await this.toggleService("altText", false);
await FirefoxCom.requestAsync("mlDelete", MLManager.#AI_ALT_TEXT_MODEL_NAME);
}
async loadModel(name) {
if (name === "altText" && this.enableAltTextModelDownload) {
await this.#loadAltTextEngine(false);
}
}
async downloadModel(name) {
if (name !== "altText" || this.enableAltTextModelDownload) {
return null;
}
this.enableAltTextModelDownload = true;
return this.#loadAltTextEngine(true);
}
async guess(data) {
if (data?.name !== "altText") {
return null;
}
const resolvers = this.#requestResolvers ||= new Set();
const resolver = Promise.withResolvers();
resolvers.add(resolver);
data.service = MLManager.#AI_ALT_TEXT_MODEL_NAME;
FirefoxCom.requestAsync("mlGuess", data).then(response => {
if (resolvers.has(resolver)) {
resolver.resolve(response);
resolvers.delete(resolver);
}
}).catch(reason => {
if (resolvers.has(resolver)) {
resolver.reject(reason);
resolvers.delete(resolver);
}
});
return resolver.promise;
}
async #cancelAllRequests() {
if (!this.#requestResolvers) {
return;
}
for (const resolver of this.#requestResolvers) {
resolver.resolve({
cancel: true
});
}
this.#requestResolvers.clear();
this.#requestResolvers = null;
}
async toggleService(name, enabled) {
if (name !== "altText" || this.enableGuessAltText === enabled) {
return;
}
this.enableGuessAltText = enabled;
if (enabled) {
if (this.enableAltTextModelDownload) {
await this.#loadAltTextEngine(false);
}
} else {
this.#cancelAllRequests();
}
}
async #loadAltTextEngine(listenToProgress) {
if (this.#enabled?.has("altText")) {
return;
}
this.#ready ||= new Set();
const promise = FirefoxCom.requestAsync("loadAIEngine", {
service: MLManager.#AI_ALT_TEXT_MODEL_NAME,
listenToProgress
}).then(ok => {
if (ok) {
this.#ready.add("altText");
}
return ok;
});
(this.#enabled ||= new Map()).set("altText", promise);
if (listenToProgress) {
const ac = new AbortController();
const signal = AbortSignal.any([this.#abortSignal, ac.signal]);
this.hasProgress = true;
window.addEventListener("loadAIEngineProgress", ({
detail
}) => {
this.#eventBus.dispatch("loadaiengineprogress", {
source: this,
detail
});
if (detail.finished) {
ac.abort();
this.hasProgress = false;
}
}, {
signal
});
promise.then(ok => {
if (!ok) {
ac.abort();
this.hasProgress = false;
}
});
}
await promise;
}
}
class ExternalServices extends BaseExternalServices {
updateFindControlState(data) {
FirefoxCom.request("updateFindControlState", data);
}
updateFindMatchesCount(data) {
FirefoxCom.request("updateFindMatchesCount", data);
}
initPassiveLoading() {
let pdfDataRangeTransport;
window.addEventListener("message", function windowMessage(e) {
if (e.source !== null) {
console.warn("Rejected untrusted message from " + e.origin);
return;
}
const args = e.data;
if (typeof args !== "object" || !("pdfjsLoadAction" in args)) {
return;
}
switch (args.pdfjsLoadAction) {
case "supportsRangedLoading":
if (args.done && !args.data) {
viewerApp._documentError(null);
break;
}
pdfDataRangeTransport = new FirefoxComDataRangeTransport(args.length, args.data, args.done, args.filename);
viewerApp.open({
range: pdfDataRangeTransport
});
break;
case "range":
pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
break;
case "rangeProgress":
pdfDataRangeTransport.onDataProgress(args.loaded);
break;
case "progressiveRead":
pdfDataRangeTransport.onDataProgressiveRead(args.chunk);
pdfDataRangeTransport.onDataProgress(args.loaded, args.total);
break;
case "progressiveDone":
pdfDataRangeTransport?.onDataProgressiveDone();
break;
case "progress":
viewerApp.progress(args.loaded / args.total);
break;
case "complete":
if (!args.data) {
viewerApp._documentError(null, {
message: args.errorCode
});
break;
}
viewerApp.open({
data: args.data,
filename: args.filename
});
break;
}
});
FirefoxCom.request("initPassiveLoading", null);
}
reportTelemetry(data) {
FirefoxCom.request("reportTelemetry", data);
}
updateEditorStates(data) {
FirefoxCom.request("updateEditorStates", data);
}
async createL10n() {
await document.l10n.ready;
return new L10n(AppOptions.get("localeProperties"), document.l10n);
}
createScripting() {
return FirefoxScripting;
}
dispatchGlobalEvent(event) {
FirefoxCom.request("dispatchGlobalEvent", event);
}
}
;// ./web/new_alt_text_manager.js
class NewAltTextManager {
#boundCancel = this.#cancel.bind(this);
#createAutomaticallyButton;
#currentEditor = null;
#cancelButton;
#descriptionContainer;
#dialog;
#disclaimer;
#downloadModel;
#downloadModelDescription;
#eventBus;
#firstTime = false;
#guessedAltText;
#hasAI = null;
#isEditing = null;
#imagePreview;
#imageData;
#isAILoading = false;
#wasAILoading = false;
#learnMore;
#notNowButton;
#overlayManager;
#textarea;
#title;
#uiManager;
#previousAltText = null;
constructor({
descriptionContainer,
dialog,
imagePreview,
cancelButton,
disclaimer,
notNowButton,
saveButton,
textarea,
learnMore,
errorCloseButton,
createAutomaticallyButton,
downloadModel,
downloadModelDescription,
title
}, overlayManager, eventBus) {
this.#cancelButton = cancelButton;
this.#createAutomaticallyButton = createAutomaticallyButton;
this.#descriptionContainer = descriptionContainer;
this.#dialog = dialog;
this.#disclaimer = disclaimer;
this.#notNowButton = notNowButton;
this.#imagePreview = imagePreview;
this.#textarea = textarea;
this.#learnMore = learnMore;
this.#title = title;
this.#downloadModel = downloadModel;
this.#downloadModelDescription = downloadModelDescription;
this.#overlayManager = overlayManager;
this.#eventBus = eventBus;
dialog.addEventListener("close", this.#close.bind(this));
dialog.addEventListener("contextmenu", event => {
if (event.target !== this.#textarea) {
event.preventDefault();
}
});
cancelButton.addEventListener("click", this.#boundCancel);
notNowButton.addEventListener("click", this.#boundCancel);
saveButton.addEventListener("click", this.#save.bind(this));
errorCloseButton.addEventListener("click", () => {
this.#toggleError(false);
});
createAutomaticallyButton.addEventListener("click", async () => {
const checked = createAutomaticallyButton.getAttribute("aria-pressed") !== "true";
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.alt_text.ai_generation_check",
data: {
status: checked
}
});
if (this.#uiManager) {
this.#uiManager.setPreference("enableGuessAltText", checked);
await this.#uiManager.mlManager.toggleService("altText", checked);
}
this.#toggleGuessAltText(checked, false);
});
textarea.addEventListener("focus", () => {
this.#wasAILoading = this.#isAILoading;
this.#toggleLoading(false);
this.#toggleTitleAndDisclaimer();
});
textarea.addEventListener("blur", () => {
if (!textarea.value) {
this.#toggleLoading(this.#wasAILoading);
}
this.#toggleTitleAndDisclaimer();
});
textarea.addEventListener("input", () => {
this.#toggleTitleAndDisclaimer();
});
eventBus._on("enableguessalttext", ({
value
}) => {
this.#toggleGuessAltText(value, false);
});
this.#overlayManager.register(dialog);
this.#learnMore.addEventListener("click", () => {
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.alt_text.info",
data: {
topic: "alt_text"
}
});
});
}
#toggleLoading(value) {
if (!this.#uiManager || this.#isAILoading === value) {
return;
}
this.#isAILoading = value;
this.#descriptionContainer.classList.toggle("loading", value);
}
#toggleError(value) {
if (!this.#uiManager) {
return;
}
this.#dialog.classList.toggle("error", value);
}
async #toggleGuessAltText(value, isInitial = false) {
if (!this.#uiManager) {
return;
}
this.#dialog.classList.toggle("aiDisabled", !value);
this.#createAutomaticallyButton.setAttribute("aria-pressed", value);
if (value) {
const {
altTextLearnMoreUrl
} = this.#uiManager.mlManager;
if (altTextLearnMoreUrl) {
this.#learnMore.href = altTextLearnMoreUrl;
}
this.#mlGuessAltText(isInitial);
} else {
this.#toggleLoading(false);
this.#isAILoading = false;
this.#toggleTitleAndDisclaimer();
}
}
#toggleNotNow() {
this.#notNowButton.classList.toggle("hidden", !this.#firstTime);
this.#cancelButton.classList.toggle("hidden", this.#firstTime);
}
#toggleAI(value) {
if (!this.#uiManager || this.#hasAI === value) {
return;
}
this.#hasAI = value;
this.#dialog.classList.toggle("noAi", !value);
this.#toggleTitleAndDisclaimer();
}
#toggleTitleAndDisclaimer() {
const visible = this.#isAILoading || this.#guessedAltText && this.#guessedAltText === this.#textarea.value;
this.#disclaimer.hidden = !visible;
const isEditing = this.#isAILoading || !!this.#textarea.value;
if (this.#isEditing === isEditing) {
return;
}
this.#isEditing = isEditing;
this.#title.setAttribute("data-l10n-id", isEditing ? "pdfjs-editor-new-alt-text-dialog-edit-label" : "pdfjs-editor-new-alt-text-dialog-add-label");
}
async #mlGuessAltText(isInitial) {
if (this.#isAILoading) {
return;
}
if (this.#textarea.value) {
return;
}
if (isInitial && this.#previousAltText !== null) {
return;
}
this.#guessedAltText = this.#currentEditor.guessedAltText;
if (this.#previousAltText === null && this.#guessedAltText) {
this.#addAltText(this.#guessedAltText);
return;
}
this.#toggleLoading(true);
this.#toggleTitleAndDisclaimer();
let hasError = false;
try {
const altText = await this.#currentEditor.mlGuessAltText(this.#imageData, false);
if (altText) {
this.#guessedAltText = altText;
this.#wasAILoading = this.#isAILoading;
if (this.#isAILoading) {
this.#addAltText(altText);
}
}
} catch (e) {
console.error(e);
hasError = true;
}
this.#toggleLoading(false);
this.#toggleTitleAndDisclaimer();
if (hasError && this.#uiManager) {
this.#toggleError(true);
}
}
#addAltText(altText) {
if (!this.#uiManager || this.#textarea.value) {
return;
}
this.#textarea.value = altText;
this.#toggleTitleAndDisclaimer();
}
#setProgress() {
this.#downloadModel.classList.toggle("hidden", false);
const callback = async ({
detail: {
finished,
total,
totalLoaded
}
}) => {
const ONE_MEGA_BYTES = 1e6;
totalLoaded = Math.min(0.99 * total, totalLoaded);
const totalSize = this.#downloadModelDescription.ariaValueMax = Math.round(total / ONE_MEGA_BYTES);
const downloadedSize = this.#downloadModelDescription.ariaValueNow = Math.round(totalLoaded / ONE_MEGA_BYTES);
this.#downloadModelDescription.setAttribute("data-l10n-args", JSON.stringify({
totalSize,
downloadedSize
}));
if (!finished) {
return;
}
this.#eventBus._off("loadaiengineprogress", callback);
this.#downloadModel.classList.toggle("hidden", true);
this.#toggleAI(true);
if (!this.#uiManager) {
return;
}
const {
mlManager
} = this.#uiManager;
mlManager.toggleService("altText", true);
this.#toggleGuessAltText(await mlManager.isEnabledFor("altText"), true);
};
this.#eventBus._on("loadaiengineprogress", callback);
}
async editAltText(uiManager, editor, firstTime) {
if (this.#currentEditor || !editor) {
return;
}
if (firstTime && editor.hasAltTextData()) {
editor.altTextFinish();
return;
}
this.#firstTime = firstTime;
let {
mlManager
} = uiManager;
let hasAI = !!mlManager;
this.#toggleTitleAndDisclaimer();
if (mlManager && !mlManager.isReady("altText")) {
hasAI = false;
if (mlManager.hasProgress) {
this.#setProgress();
} else {
mlManager = null;
}
} else {
this.#downloadModel.classList.toggle("hidden", true);
}
const isAltTextEnabledPromise = mlManager?.isEnabledFor("altText");
this.#currentEditor = editor;
this.#uiManager = uiManager;
this.#uiManager.removeEditListeners();
({
altText: this.#previousAltText
} = editor.altTextData);
this.#textarea.value = this.#previousAltText ?? "";
const AI_MAX_IMAGE_DIMENSION = 224;
const MAX_PREVIEW_DIMENSION = 180;
let canvas, width, height;
if (mlManager) {
({
canvas,
width,
height,
imageData: this.#imageData
} = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, MAX_PREVIEW_DIMENSION, true));
if (hasAI) {
this.#toggleGuessAltText(await isAltTextEnabledPromise, true);
}
} else {
({
canvas,
width,
height
} = editor.copyCanvas(AI_MAX_IMAGE_DIMENSION, MAX_PREVIEW_DIMENSION, false));
}
canvas.setAttribute("role", "presentation");
const {
style
} = canvas;
style.width = `${width}px`;
style.height = `${height}px`;
this.#imagePreview.append(canvas);
this.#toggleNotNow();
this.#toggleAI(hasAI);
this.#toggleError(false);
try {
await this.#overlayManager.open(this.#dialog);
} catch (ex) {
this.#close();
throw ex;
}
}
#cancel() {
this.#currentEditor.altTextData = {
cancel: true
};
const altText = this.#textarea.value.trim();
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.alt_text.dismiss",
data: {
alt_text_type: altText ? "present" : "empty",
flow: this.#firstTime ? "image_add" : "alt_text_edit"
}
});
this.#currentEditor._reportTelemetry({
action: "pdfjs.image.image_added",
data: {
alt_text_modal: true,
alt_text_type: "skipped"
}
});
this.#finish();
}
#finish() {
if (this.#overlayManager.active === this.#dialog) {
this.#overlayManager.close(this.#dialog);
}
}
#close() {
const canvas = this.#imagePreview.firstChild;
canvas.remove();
canvas.width = canvas.height = 0;
this.#imageData = null;
this.#toggleLoading(false);
this.#uiManager?.addEditListeners();
this.#currentEditor.altTextFinish();
this.#uiManager?.setSelected(this.#currentEditor);
this.#currentEditor = null;
this.#uiManager = null;
}
#extractWords(text) {
return new Set(text.toLowerCase().split(/[^\p{L}\p{N}]+/gu).filter(x => !!x));
}
#save() {
const altText = this.#textarea.value.trim();
this.#currentEditor.altTextData = {
altText,
decorative: false
};
this.#currentEditor.altTextData.guessedAltText = this.#guessedAltText;
if (this.#guessedAltText && this.#guessedAltText !== altText) {
const guessedWords = this.#extractWords(this.#guessedAltText);
const words = this.#extractWords(altText);
--> --------------------
--> maximum size reached
--> --------------------
[ zur Elbe Produktseite wechseln0.107Quellennavigators
]
|
2026-04-02
|