ngx-extended-pdf-viewer
Version:
Embedding PDF files in your Angular application. Highly configurable viewer including the toolbar, sidebar, and all the features you're used to.
1,148 lines • 359 kB
JavaScript
import { isPlatformBrowser } from '@angular/common';
import { ChangeDetectionStrategy, Component, EventEmitter, HostListener, Inject, Input, Output, PLATFORM_ID, ViewChild, } from '@angular/core';
import { PdfCursorTools } from './options/pdf-cursor-tools';
import { assetsUrl, getVersionSuffix, pdfDefaultOptions } from './options/pdf-default-options';
import { ScrollModeType } from './options/pdf-viewer';
import { VerbosityLevel } from './options/verbosity-level';
import { PdfDummyComponentsComponent } from './pdf-dummy-components/pdf-dummy-components.component';
import { UnitToPx } from './unit-to-px';
import { NgxFormSupport } from './ngx-form-support';
import { PdfSidebarView } from './options/pdf-sidebar-views';
import * as i0 from "@angular/core";
import * as i1 from "./pdf-notification-service";
import * as i2 from "@angular/common";
import * as i3 from "./ngx-extended-pdf-viewer.service";
import * as i4 from "./dynamic-css/dynamic-css.component";
import * as i5 from "./theme/acroform-default-theme/pdf-acroform-default-theme.component";
import * as i6 from "./toolbar/pdf-context-menu/pdf-context-menu.component";
import * as i7 from "./theme/pdf-dark-theme/pdf-dark-theme.component";
import * as i8 from "./pdf-dialog/pdf-alt-text-dialog/pdf-alt-text-dialog.component";
import * as i9 from "./pdf-dialog/pdf-document-properties-dialog/pdf-document-properties-dialog.component";
import * as i10 from "./pdf-dummy-components/pdf-dummy-components.component";
import * as i11 from "./pdf-dialog/pdf-error-message/pdf-error-message.component";
import * as i12 from "./toolbar/pdf-findbar/pdf-findbar.component";
import * as i13 from "./theme/pdf-light-theme/pdf-light-theme.component";
import * as i14 from "./pdf-dialog/pdf-password-dialog/pdf-password-dialog.component";
import * as i15 from "./pdf-dialog/pdf-prepare-printing-dialog/pdf-prepare-printing-dialog.component";
import * as i16 from "./secondary-toolbar/pdf-secondary-toolbar/pdf-secondary-toolbar.component";
import * as i17 from "./sidebar/pdf-sidebar/pdf-sidebar.component";
import * as i18 from "./toolbar/pdf-toolbar/pdf-toolbar.component";
import * as i19 from "./translate.pipe";
function isIOS() {
if (typeof window === 'undefined') {
// server-side rendering
return false;
}
return (['iPad Simulator', 'iPhone Simulator', 'iPod Simulator', 'iPad', 'iPhone', 'iPod'].includes(navigator.platform) ||
// iPad on iOS 13 detection
(navigator.userAgent.includes('Mac') && 'ontouchend' in document));
}
export class NgxExtendedPdfViewerComponent {
ngZone;
platformId;
notificationService;
location;
elementRef;
platformLocation;
cdr;
service;
renderer;
static originalPrint = typeof window !== 'undefined' ? window.print : undefined;
ngxExtendedPdfViewerIncompletelyInitialized = true;
formSupport = new NgxFormSupport();
/**
* The dummy components are inserted automatically when the user customizes the toolbar
* without adding every original toolbar item. Without the dummy components, the
* initialization code of pdf.js crashes because it assume that every standard widget is there.
*/
dummyComponents;
root;
/* UI templates */
customFindbarInputArea;
customToolbar;
customFindbar;
customFindbarButtons;
customPdfViewer;
customSecondaryToolbar;
customSidebar;
customThumbnail;
customFreeFloatingBar;
showFreeFloatingBar = true;
enableDragAndDrop = true;
localizationInitialized = false;
set formData(formData) {
this.formSupport.formData = formData;
}
disableForms = false;
get formDataChange() {
return this.formSupport.formDataChange;
}
_pageViewMode = 'multiple';
baseHref;
/** This flag prevents trying to load a file twice if the user uploads it using the file upload dialog or via drag'n'drop */
srcChangeTriggeredByUser = false;
get pageViewMode() {
return this._pageViewMode;
}
set pageViewMode(viewMode) {
if (isPlatformBrowser(this.platformId)) {
const hasChanged = this._pageViewMode !== viewMode;
if (hasChanged) {
const mustRedraw = !this.ngxExtendedPdfViewerIncompletelyInitialized && (this._pageViewMode === 'book' || viewMode === 'book');
this._pageViewMode = viewMode;
this.pageViewModeChange.emit(this._pageViewMode);
const PDFViewerApplicationOptions = window.PDFViewerApplicationOptions;
PDFViewerApplicationOptions?.set('pageViewMode', this.pageViewMode);
const PDFViewerApplication = window.PDFViewerApplication;
if (PDFViewerApplication) {
PDFViewerApplication.pdfViewer.pageViewMode = this._pageViewMode;
PDFViewerApplication.findController.pageViewMode = this._pageViewMode;
}
if (viewMode === 'infinite-scroll') {
if (this.scrollMode === ScrollModeType.page || this.scrollMode === ScrollModeType.horizontal) {
this.scrollMode = ScrollModeType.vertical;
PDFViewerApplication.eventBus.dispatch('switchscrollmode', { mode: Number(this.scrollMode) });
}
this.removeScrollbarInInfiniteScrollMode(false);
}
else if (viewMode !== 'multiple') {
this.scrollMode = ScrollModeType.vertical;
}
else {
if (this.scrollMode === ScrollModeType.page) {
this.scrollMode = ScrollModeType.vertical;
}
this.removeScrollbarInInfiniteScrollMode(true);
}
if (viewMode === 'single') {
// since pdf.js, our custom single-page-mode has been replaced by the standard scrollMode="page"
this.scrollMode = ScrollModeType.page;
this._pageViewMode = viewMode;
}
if (viewMode === 'book') {
this.showBorders = false;
if (this.scrollMode !== ScrollModeType.vertical) {
this.scrollMode = ScrollModeType.vertical;
}
}
if (mustRedraw) {
if (viewMode !== 'book') {
const ngx = this.elementRef.nativeElement;
const viewerContainer = ngx.querySelector('#viewerContainer');
viewerContainer.style.width = '';
viewerContainer.style.overflow = '';
viewerContainer.style.marginRight = '';
viewerContainer.style.marginLeft = '';
const viewer = ngx.querySelector('#viewer');
viewer.style.maxWidth = '';
viewer.style.minWidth = '';
}
this.openPDF2();
}
}
}
}
pageViewModeChange = new EventEmitter();
progress = new EventEmitter();
secondaryToolbarComponent;
sidebarComponent;
/* regular attributes */
_src;
srcChange = new EventEmitter();
_scrollMode = ScrollModeType.vertical;
get scrollMode() {
return this._scrollMode;
}
set scrollMode(value) {
if (this._scrollMode !== value) {
const PDFViewerApplication = window.PDFViewerApplication;
if (PDFViewerApplication?.pdfViewer) {
if (PDFViewerApplication.pdfViewer.scrollMode !== Number(this.scrollMode)) {
PDFViewerApplication.eventBus.dispatch('switchscrollmode', { mode: Number(this.scrollMode) });
}
}
this._scrollMode = value;
if (this._scrollMode === ScrollModeType.page) {
if (this.pageViewMode !== 'single') {
this._pageViewMode = 'single';
this.pageViewModeChange.emit(this.pageViewMode);
}
}
else if (this.pageViewMode === 'single' || this._scrollMode === ScrollModeType.horizontal) {
this._pageViewMode = 'multiple';
this.pageViewModeChange.emit(this.pageViewMode);
}
}
}
scrollModeChange = new EventEmitter();
authorization = undefined;
httpHeaders = undefined;
contextMenuAllowed = true;
afterPrint = new EventEmitter();
beforePrint = new EventEmitter();
currentZoomFactor = new EventEmitter();
/** This field stores the previous zoom level if the page is enlarged with a double-tap or double-click */
previousZoom;
enablePrint = true;
/**
* Number of milliseconds to wait between initializing the PDF viewer and loading the PDF file.
* Most users can let this parameter safely at it's default value of zero.
* Set this to 1000 or higher if you run into timing problems (typically caused by loading the locale files
* after the PDF files, so they are not available when the PDF viewer is initialized).
*/
delayFirstView = 0;
showTextEditor = true;
showStampEditor = true;
showDrawEditor = true;
showHighlightEditor = true;
/** store the timeout id so it can be canceled if user leaves the page before the PDF is shown */
initTimeout;
/** How many log messages should be printed?
* Legal values: VerbosityLevel.INFOS (= 5), VerbosityLevel.WARNINGS (= 1), VerbosityLevel.ERRORS (= 0) */
logLevel = VerbosityLevel.WARNINGS;
relativeCoordsOptions = {};
/** Use the minified (minifiedJSLibraries="true", which is the default) or the user-readable pdf.js library (minifiedJSLibraries="false") */
minifiedJSLibraries = true;
primaryMenuVisible = true;
/** option to increase (or reduce) print resolution. Default is 150 (dpi). Sensible values
* are 300, 600, and 1200. Note the increase memory consumption, which may even result in a browser crash. */
printResolution = null;
rotation;
rotationChange = new EventEmitter();
annotationLayerRendered = new EventEmitter();
annotationEditorLayerRendered = new EventEmitter();
xfaLayerRendered = new EventEmitter();
outlineLoaded = new EventEmitter();
attachmentsloaded = new EventEmitter();
layersloaded = new EventEmitter();
hasSignature;
set src(url) {
if (url instanceof Uint8Array) {
this._src = url.buffer;
}
else if (url instanceof URL) {
this._src = url.toString();
}
else if (typeof Blob !== 'undefined' && url instanceof Blob) {
// additional check introduced to support server side rendering
const reader = new FileReader();
reader.onloadend = () => {
setTimeout(() => {
this.src = new Uint8Array(reader.result);
if (this.service.ngxExtendedPdfViewerInitialized) {
if (this.ngxExtendedPdfViewerIncompletelyInitialized) {
this.openPDF();
}
else {
(async () => this.openPDF2())();
}
// else openPDF is called later, so we do nothing to prevent loading the PDF file twice
}
});
};
reader.readAsArrayBuffer(url);
}
else if (typeof url === 'string') {
this._src = url;
if (url.length > 980) {
// minimal length of a base64 encoded PDF
if (url.length % 4 === 0) {
if (/^[a-zA-Z\d/+]+={0,2}$/.test(url)) {
console.error('The URL looks like a base64 encoded string. If so, please use the attribute [base64Src] instead of [src]');
}
}
}
}
else {
this._src = url;
}
}
set base64Src(base64) {
if (base64) {
if (typeof window === 'undefined') {
// server-side rendering
return;
}
const binary_string = atob(base64);
const len = binary_string.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
this.src = bytes.buffer;
}
else {
this._src = undefined;
}
}
/**
* The combination of height, minHeight, and autoHeight ensures the PDF height of the PDF viewer is calculated correctly when the height is a percentage.
* By default, many CSS frameworks make a div with 100% have a height or zero pixels. checkHeigth() fixes this.
*/
autoHeight = false;
minHeight = undefined;
_height = '100%';
set height(h) {
this.minHeight = undefined;
this.autoHeight = false;
if (h) {
if (h === 'auto') {
this.autoHeight = true;
this._height = undefined;
}
else {
this._height = h;
}
}
else {
this.height = '100%';
}
setTimeout(() => {
this.checkHeight();
});
}
get height() {
return this._height;
}
forceUsingLegacyES5 = false;
backgroundColor = '#e8e8eb';
/** Allows the user to define the name of the file after clicking "download" */
filenameForDownload = undefined;
/** Allows the user to disable the keyboard bindings completely */
ignoreKeyboard = false;
/** Allows the user to disable a list of key bindings. */
ignoreKeys = [];
/** Allows the user to enable a list of key bindings explicitly. If this property is set, every other key binding is ignored. */
acceptKeys = [];
/** Allows the user to put the viewer's svg images into an arbitrary folder */
imageResourcesPath = assetsUrl(pdfDefaultOptions.assetsFolder) + '/images/';
/** Allows the user to put their locale folder into an arbitrary folder */
localeFolderPath = assetsUrl(pdfDefaultOptions.assetsFolder) + '/locale';
/** Override the default locale. This must be the complete locale name, such as "es-ES". The string is allowed to be all lowercase.
*/
language = undefined;
/** By default, listening to the URL is deactivated because often the anchor tag is used for the Angular router */
listenToURL = false;
/** Navigate to a certain "named destination" */
nameddest = undefined;
/** allows you to pass a password to read password-protected files */
password = undefined;
replaceBrowserPrint = true;
_showSidebarButton = true;
viewerPositionTop = '32px';
/** pdf.js can show signatures, but fails to verify them. So they are switched off by default.
* Set "[showUnverifiedSignatures]"="true" to display e-signatures nonetheless.
*/
showUnverifiedSignatures = false;
startTabindex;
get showSidebarButton() {
return this._showSidebarButton;
}
set showSidebarButton(show) {
if (typeof window === 'undefined') {
// server-side rendering
this._showSidebarButton = false;
return;
}
this._showSidebarButton = show;
if (this._showSidebarButton) {
const isIE = /msie\s|trident\//i.test(window.navigator.userAgent);
let factor = 1;
if (isIE) {
factor = Number((this._mobileFriendlyZoom || '100').replace('%', '')) / 100;
}
this.findbarLeft = (68 * factor).toString() + 'px';
return;
}
this.findbarLeft = '0px';
}
_sidebarVisible = undefined;
get sidebarVisible() {
return this._sidebarVisible;
}
set sidebarVisible(value) {
if (value !== this._sidebarVisible) {
this.sidebarVisibleChange.emit(value);
}
this._sidebarVisible = value;
const PDFViewerApplication = window.PDFViewerApplication;
if (PDFViewerApplication?.pdfSidebar) {
if (this.sidebarVisible) {
PDFViewerApplication.pdfSidebar.open();
const view = Number(this.activeSidebarView);
if (view === 1 || view === 2 || view === 3 || view === 4) {
PDFViewerApplication.pdfSidebar.switchView(view, true);
}
else {
console.error('[activeSidebarView] must be an integer value between 1 and 4');
}
}
else {
PDFViewerApplication.pdfSidebar.close();
}
}
}
sidebarVisibleChange = new EventEmitter();
activeSidebarView = PdfSidebarView.OUTLINE;
activeSidebarViewChange = new EventEmitter();
findbarVisible = false;
findbarVisibleChange = new EventEmitter();
propertiesDialogVisible = false;
propertiesDialogVisibleChange = new EventEmitter();
showFindButton = undefined;
showFindHighlightAll = true;
showFindMatchCase = true;
showFindCurrentPageOnly = true;
showFindPageRange = true;
showFindEntireWord = true;
showFindEntirePhrase = true;
showFindMatchDiacritics = true;
showFindFuzzySearch = true;
showFindResultsCount = true;
showFindMessages = true;
showPagingButtons = true;
showZoomButtons = true;
showPresentationModeButton = false;
showOpenFileButton = true;
showPrintButton = true;
showDownloadButton = true;
theme = 'light';
showToolbar = true;
showSecondaryToolbarButton = true;
showSinglePageModeButton = true;
showVerticalScrollButton = true;
showHorizontalScrollButton = true;
showWrappedScrollButton = true;
showInfiniteScrollButton = true;
showBookModeButton = true;
showRotateButton = true;
_handTool = !isIOS();
set handTool(handTool) {
if (isIOS() && handTool) {
console.log("On iOS, the handtool doesn't work reliably. Plus, you don't need it because touch gestures allow you to distinguish easily between swiping and selecting text. Therefore, the library ignores your setting.");
return;
}
this._handTool = handTool;
}
get handTool() {
return this._handTool;
}
handToolChange = new EventEmitter();
showHandToolButton = false;
_showScrollingButton = true;
get showScrollingButton() {
if (this.pageViewMode === 'multiple') {
return this._showScrollingButton;
}
return false;
}
set showScrollingButton(val) {
this._showScrollingButton = val;
}
showSpreadButton = true;
showPropertiesButton = true;
showBorders = true;
spread;
spreadChange = new EventEmitter();
thumbnailDrawn = new EventEmitter();
_page = undefined;
get page() {
return this._page;
}
set page(p) {
if (p) {
// silently cope with strings
this._page = Number(p);
}
else {
this._page = undefined;
}
}
pageChange = new EventEmitter();
pageLabel = undefined;
pageLabelChange = new EventEmitter();
pagesLoaded = new EventEmitter();
pageRender = new EventEmitter();
pageRendered = new EventEmitter();
pdfDownloaded = new EventEmitter();
pdfLoaded = new EventEmitter();
pdfLoadingStarts = new EventEmitter();
pdfLoadingFailed = new EventEmitter();
textLayer = undefined;
textLayerRendered = new EventEmitter();
annotationEditorModeChanged = new EventEmitter();
updateFindMatchesCount = new EventEmitter();
updateFindState = new EventEmitter();
/** Legal values: undefined, 'auto', 'page-actual', 'page-fit', 'page-width', or '50' (or any other percentage) */
zoom = undefined;
zoomChange = new EventEmitter();
zoomLevels = ['auto', 'page-actual', 'page-fit', 'page-width', 0.5, 1, 1.25, 1.5, 2, 3, 4];
maxZoom = 10;
minZoom = 0.1;
/** This attribute allows you to increase the size of the UI elements so you can use them on small mobile devices.
* This attribute is a string with a percent character at the end (e.g. "150%").
*/
_mobileFriendlyZoom = '100%';
mobileFriendlyZoomScale = 1;
toolbarMarginTop = '0px';
toolbarWidth = '100%';
toolbar = undefined;
onToolbarLoaded(toolbarElement) {
this.toolbar = toolbarElement;
}
toolbarWidthInPixels = 3.14159265359; // magic number indicating the toolbar size hasn't been determined yet
secondaryToolbarTop = undefined;
sidebarPositionTop = undefined;
// dirty IE11 hack - temporary solution
findbarTop = undefined;
// dirty IE11 hack - temporary solution
findbarLeft = undefined;
get mobileFriendlyZoom() {
return this._mobileFriendlyZoom;
}
get pdfJsVersion() {
return getVersionSuffix(pdfDefaultOptions.assetsFolder);
}
get majorMinorPdfJsVersion() {
const fullVersion = this.pdfJsVersion;
const pos = fullVersion.lastIndexOf('.');
return fullVersion.substring(0, pos).replace('.', '-');
}
/**
* This attributes allows you to increase the size of the UI elements so you can use them on small mobile devices.
* This attribute is a string with a percent character at the end (e.g. "150%").
*/
set mobileFriendlyZoom(zoom) {
// tslint:disable-next-line:triple-equals - the type conversion is intended
if (zoom == 'true') {
zoom = '150%';
// tslint:disable-next-line:triple-equals - the type conversion is intended
}
else if (zoom == 'false' || zoom === undefined || zoom === null) {
zoom = '100%';
}
this._mobileFriendlyZoom = zoom;
let factor = 1;
if (!String(zoom).includes('%')) {
zoom = 100 * Number(zoom) + '%';
}
factor = Number((zoom || '100').replace('%', '')) / 100;
this.mobileFriendlyZoomScale = factor;
this.toolbarWidth = (100 / factor).toString() + '%';
this.toolbarMarginTop = (factor - 1) * 16 + 'px';
setTimeout(() => this.calcViewerPositionTop());
}
shuttingDown = false;
serverSideRendering = true;
calcViewerPositionTop() {
if (this.toolbar === undefined) {
this.sidebarPositionTop = '0';
return;
}
let top = this.toolbar.getBoundingClientRect().height;
if (top < 33) {
this.viewerPositionTop = '33px';
}
else {
this.viewerPositionTop = top + 'px';
}
const factor = top / 33;
if (this.primaryMenuVisible) {
this.sidebarPositionTop = (33 + 33 * (factor - 1)).toString() + 'px';
}
else {
this.sidebarPositionTop = '0';
}
this.secondaryToolbarTop = (33 + 38 * (factor - 1)).toString() + 'px';
this.findbarTop = (33 + 38 * (factor - 1)).toString() + 'px';
const findButton = document.getElementById('primaryViewFind');
if (findButton) {
const containerPositionLeft = this.toolbar.getBoundingClientRect().left;
const findButtonPosition = findButton.getBoundingClientRect();
const left = Math.max(0, findButtonPosition.left - containerPositionLeft);
this.findbarLeft = left + 'px';
}
else if (this.showSidebarButton) {
this.findbarLeft = 34 + (32 * factor).toString() + 'px';
}
else {
this.findbarLeft = '0';
}
}
constructor(ngZone, platformId, notificationService, location, elementRef, platformLocation, cdr, service, renderer) {
this.ngZone = ngZone;
this.platformId = platformId;
this.notificationService = notificationService;
this.location = location;
this.elementRef = elementRef;
this.platformLocation = platformLocation;
this.cdr = cdr;
this.service = service;
this.renderer = renderer;
this.baseHref = this.platformLocation.getBaseHrefFromDOM();
this.service.recalculateSize$.subscribe(() => this.onResize());
if (isPlatformBrowser(this.platformId)) {
this.serverSideRendering = false;
this.toolbarWidth = String(document.body.clientWidth);
}
}
iOSVersionRequiresES5() {
if (typeof window === 'undefined') {
// server-side rendering
return false;
}
const match = navigator.appVersion.match(/OS (\d+)_(\d+)_?(\d+)?/);
if (match !== undefined && match !== null) {
return parseInt(match[1], 10) < 14;
}
return false;
}
async needsES5() {
if (typeof window === 'undefined') {
// server-side rendering
return false;
}
const isIE = !!window.MSInputMethodContext && !!document.documentMode;
const isEdge = /Edge\/\d./i.test(navigator.userAgent);
const isIOs13OrBelow = this.iOSVersionRequiresES5();
let needsES5 = typeof ReadableStream === 'undefined' || typeof Promise['allSettled'] === 'undefined';
if (needsES5 || isIE || isEdge || isIOs13OrBelow || this.forceUsingLegacyES5) {
return true;
}
return !(await this.supportsOptionalChaining());
}
supportsOptionalChaining() {
return new Promise((resolve) => {
const support = window.supportsOptionalChaining;
support !== undefined ? resolve(support) : resolve(this.addScriptOpChainingSupport());
});
}
addScriptOpChainingSupport() {
return new Promise((resolve) => {
const script = this.createScriptElement(pdfDefaultOptions.assetsFolder + '/op-chaining-support.js');
script.onload = () => {
script.remove();
resolve(window.supportsOptionalChaining);
};
script.onerror = () => {
script.remove();
window.supportsOptionalChaining = false;
resolve(false);
};
document.body.appendChild(script);
});
}
createScriptElement(sourcePath) {
const script = document.createElement('script');
script.async = true;
script.type = sourcePath.endsWith('.mjs') ? 'module' : 'text/javascript';
const ttWindow = window;
if (ttWindow.trustedTypes) {
const sanitizer = ttWindow.trustedTypes.createPolicy('foo', {
createScriptURL: (input) => input,
});
script.src = sanitizer.createScriptURL(this.location.normalize(sourcePath));
}
else {
script.src = this.location.normalize(sourcePath);
}
return script;
}
getPdfJsPath(artifact, needsES5) {
let suffix = this.minifiedJSLibraries ? '.min.js' : '.js';
const assets = pdfDefaultOptions.assetsFolder;
const versionSuffix = getVersionSuffix(assets);
if (versionSuffix.startsWith('4')) {
suffix = suffix.replace('.js', '.mjs');
}
const artifactPath = `/${artifact}-`;
const es5 = needsES5 ? '-es5' : '';
return assets + artifactPath + versionSuffix + es5 + suffix;
}
loadViewer() {
globalThis['ngxZone'] = this.ngZone;
this.ngZone.runOutsideAngular(() => {
this.needsES5().then((needsES5) => {
const viewerPath = this.getPdfJsPath('viewer', needsES5);
const script = this.createScriptElement(viewerPath);
document.getElementsByTagName('head')[0].appendChild(script);
});
});
}
addFeatures() {
return new Promise((resolve) => {
const script = this.createScriptElement(pdfDefaultOptions.assetsFolder + '/additional-features.js');
script.onload = () => {
script.remove();
};
script.onerror = () => {
script.remove();
resolve();
};
document.body.appendChild(script);
});
}
ngOnInit() {
if (isPlatformBrowser(this.platformId)) {
globalThis['setNgxExtendedPdfViewerSource'] = (url) => {
this._src = url;
this.srcChangeTriggeredByUser = true;
this.srcChange.emit(url);
};
this.addTranslationsUnlessProvidedByTheUser();
this.formSupport.registerFormSupportWithPdfjs(this.ngZone);
this.loadPdfJs();
this.hideToolbarIfItIsEmpty();
}
}
loadPdfJs() {
globalThis['ngxZone'] = this.ngZone;
this.ngZone.runOutsideAngular(() => {
if (!globalThis['pdfjs-dist/build/pdf']) {
this.needsES5().then((needsES5) => {
if (needsES5) {
if (!pdfDefaultOptions.needsES5) {
console.log("If you see the error message \"expected expression, got '='\" above: you can safely ignore it as long as you know what you're doing. It means your browser is out-of-date. Please update your browser to benefit from the latest security updates and to enjoy a faster PDF viewer.");
}
pdfDefaultOptions.needsES5 = true;
console.log('Using the ES5 version of the PDF viewer. Your PDF files show faster if you update your browser.');
}
if (this.minifiedJSLibraries) {
if (!pdfDefaultOptions.workerSrc().endsWith('.min.js')) {
const src = pdfDefaultOptions.workerSrc();
pdfDefaultOptions.workerSrc = () => src.replace('.js', '.min.js');
}
}
const pdfJsPath = this.getPdfJsPath('pdf', needsES5);
if (pdfJsPath.endsWith('.mjs')) {
const src = pdfDefaultOptions.workerSrc();
pdfDefaultOptions.workerSrc = () => src.replace('.js', '.mjs');
}
const script = this.createScriptElement(pdfJsPath);
script.onload = () => {
if (!globalThis.webViewerLoad) {
this.loadViewer();
}
};
document.getElementsByTagName('head')[0].appendChild(script);
});
}
else if (!globalThis.webViewerLoad) {
this.loadViewer();
}
});
}
ngAfterViewInit() {
if (typeof window !== 'undefined') {
if (!this.shuttingDown) {
// hurried users sometimes reload the PDF before it has finished initializing
if (globalThis.webViewerLoad) {
this.ngZone.runOutsideAngular(() => this.doInitPDFViewer());
}
else {
setTimeout(() => this.ngAfterViewInit(), 50);
}
}
}
}
assignTabindexes() {
if (this.startTabindex) {
const r = this.root.nativeElement.cloneNode(true);
r.classList.add('offscreen');
this.showElementsRecursively(r);
document.body.appendChild(r);
const elements = this.collectElementPositions(r, this.root.nativeElement, []);
document.body.removeChild(r);
const topRightGreaterThanBottomLeftComparator = (a, b) => {
if (a.y - b.y > 15) {
return 1;
}
if (b.y - a.y > 15) {
return -1;
}
return a.x - b.x;
};
const sorted = [...elements].sort(topRightGreaterThanBottomLeftComparator);
for (let i = 0; i < sorted.length; i++) {
sorted[i].element.tabIndex = this.startTabindex + i;
}
}
}
showElementsRecursively(root) {
root.classList.remove('hidden');
root.classList.remove('invisible');
root.classList.remove('hiddenXXLView');
root.classList.remove('hiddenXLView');
root.classList.remove('hiddenLargeView');
root.classList.remove('hiddenMediumView');
root.classList.remove('hiddenSmallView');
root.classList.remove('hiddenTinyView');
root.classList.remove('visibleXXLView');
root.classList.remove('visibleXLView');
root.classList.remove('visibleLargeView');
root.classList.remove('visibleMediumView');
root.classList.remove('visibleSmallView');
root.classList.remove('visibleTinyView');
if (root instanceof HTMLButtonElement || root instanceof HTMLAnchorElement || root instanceof HTMLInputElement || root instanceof HTMLSelectElement) {
return;
}
else if (root.childElementCount > 0) {
for (let i = 0; i < root.childElementCount; i++) {
const c = root.children.item(i);
if (c) {
this.showElementsRecursively(c);
}
}
}
}
collectElementPositions(copy, original, elements) {
if (copy instanceof HTMLButtonElement || copy instanceof HTMLAnchorElement || copy instanceof HTMLInputElement || copy instanceof HTMLSelectElement) {
const rect = copy.getBoundingClientRect();
const elementAndPos = {
element: original,
x: Math.round(rect.left),
y: Math.round(rect.top),
};
elements.push(elementAndPos);
}
else if (copy.childElementCount > 0) {
for (let i = 0; i < copy.childElementCount; i++) {
const c = copy.children.item(i);
const o = original.children.item(i);
if (c && o) {
elements = this.collectElementPositions(c, o, elements);
}
}
}
return elements;
}
doInitPDFViewer() {
if (typeof window === 'undefined') {
// server-side rendering
return;
}
const initializeViewerAndOpenPdf = () => {
document.removeEventListener('localized', initializeViewerAndOpenPdf);
this.localizationInitialized = true;
this.initTimeout = setTimeout(() => {
if (!this.shuttingDown) {
// hurried users sometimes reload the PDF before it has finished initializing
this.calcViewerPositionTop();
this.afterLibraryInit();
this.openPDF();
this.assignTabindexes();
if (this.replaceBrowserPrint) {
window.print = window.printPDF;
}
}
}, this.delayFirstView);
};
window.addEventListener('afterprint', () => {
this.afterPrint.emit();
});
window.addEventListener('beforeprint', () => {
this.beforePrint.emit();
});
document.addEventListener('localized', initializeViewerAndOpenPdf);
if (this.service.ngxExtendedPdfViewerInitialized) {
// tslint:disable-next-line:quotemark
console.error("You're trying to open two instances of the PDF viewer. Most likely, this will result in errors.");
}
const onLoaded = () => {
this.overrideDefaultSettings();
document.removeEventListener('webviewerloaded', onLoaded);
if (this.pdfJsVersion >= '4') {
initializeViewerAndOpenPdf();
}
};
document.addEventListener('webviewerloaded', onLoaded);
this.activateTextlayerIfNecessary(null);
setTimeout(() => {
if (!this.shuttingDown) {
// hurried users sometimes reload the PDF before it has finished initializing
// This initializes the webviewer, the file may be passed in to it to initialize the viewer with a pdf directly
this.onResize();
this.hideToolbarIfItIsEmpty();
this.dummyComponents.addMissingStandardWidgets();
this.ngZone.runOutsideAngular(() => globalThis.webViewerLoad());
const PDFViewerApplication = window.PDFViewerApplication;
PDFViewerApplication.appConfig.defaultUrl = ''; // IE bugfix
if (this.filenameForDownload) {
PDFViewerApplication.appConfig.filenameForDownload = this.filenameForDownload;
}
const PDFViewerApplicationOptions = window.PDFViewerApplicationOptions;
PDFViewerApplicationOptions.set('enableDragAndDrop', this.enableDragAndDrop);
let language = this.language === '' ? undefined : this.language;
if (!language) {
if (typeof window === 'undefined') {
// server-side rendering
language = 'en';
}
else {
language = navigator.language;
}
}
PDFViewerApplicationOptions.set('locale', language);
PDFViewerApplicationOptions.set('imageResourcesPath', this.imageResourcesPath);
PDFViewerApplicationOptions.set('minZoom', this.minZoom);
PDFViewerApplicationOptions.set('maxZoom', this.maxZoom);
PDFViewerApplicationOptions.set('pageViewMode', this.pageViewMode);
PDFViewerApplicationOptions.set('verbosity', this.logLevel);
PDFViewerApplicationOptions.set('initialZoom', this.zoom);
PDFViewerApplication.isViewerEmbedded = true;
if (PDFViewerApplication.printKeyDownListener) {
window.addEventListener('keydown', PDFViewerApplication.printKeyDownListener, true);
}
const body = document.getElementsByTagName('body');
if (body[0]) {
const topLevelElements = body[0].children;
for (let i = topLevelElements.length - 1; i >= 0; i--) {
const e = topLevelElements.item(i);
if (e && e.id === 'printContainer') {
body[0].removeChild(e);
}
}
}
const pc = document.getElementById('printContainer');
if (pc) {
document.getElementsByTagName('body')[0].appendChild(pc);
}
}
}, 0);
}
addTranslationsUnlessProvidedByTheUser() {
const link = this.renderer.createElement('link');
link.rel = 'resource';
link.type = 'application/l10n';
link.href = this.localeFolderPath + '/locale.json';
link.setAttribute('origin', 'ngx-extended-pdf-viewer');
this.renderer.appendChild(this.elementRef.nativeElement, link);
}
hideToolbarIfItIsEmpty() {
this.primaryMenuVisible = this.showToolbar;
if (!this.showSecondaryToolbarButton || this.service.secondaryMenuIsEmpty) {
if (!this.isPrimaryMenuVisible()) {
this.primaryMenuVisible = false;
}
}
}
/** Notifies every widget that implements onLibraryInit() that the PDF viewer objects are available */
afterLibraryInit() {
this.notificationService.onPDFJSInit.next();
}
checkHeight() {
if (this._height) {
if (isNaN(Number(this._height.replace('%', '')))) {
// The height is defined with one of the units vh, vw, em, rem, etc.
// So the height check isn't necessary.
return;
}
}
if (document.querySelector('[data-pdfjsprinting]')) {
// #1702 workaround to a Firefox bug: when printing, container.clientHeight is temporarily 0,
// causing ngx-extended-pdf-viewer to default to 100 pixels height. So it's better
// to do nothing.
return;
}
if (typeof document !== 'undefined') {
const container = document.getElementsByClassName('zoom')[0];
if (container) {
if (container.clientHeight === 0) {
if (this.logLevel >= VerbosityLevel.WARNINGS && !this.autoHeight) {
console.warn("The height of the PDF viewer widget is zero pixels. Please check the height attribute. Is there a syntax error? Or are you using a percentage with a CSS framework that doesn't support this? The height is adjusted automatedly.");
}
this.autoHeight = true;
}
if (this.autoHeight) {
const available = window.innerHeight;
const rect = container.getBoundingClientRect();
const top = rect.top;
let maximumHeight = available - top;
// take the margins and paddings of the parent containers into account
const padding = this.calculateBorderMargin(container);
maximumHeight -= padding;
if (maximumHeight > 100) {
this.minHeight = `${maximumHeight}px`;
}
else {
this.minHeight = '100px';
}
this.cdr.markForCheck();
}
}
}
}
calculateBorderMargin(container) {
if (container) {
const computedStyle = window.getComputedStyle(container);
const padding = UnitToPx.toPx(computedStyle.paddingBottom);
const margin = UnitToPx.toPx(computedStyle.marginBottom);
if (container.style.zIndex) {
return padding + margin;
}
return padding + margin + this.calculateBorderMargin(container.parentElement);
}
return 0;
}
onSpreadChange(newSpread) {
this.spreadChange.emit(newSpread);
}
activateTextlayerIfNecessary(options) {
if (this.textLayer === undefined) {
if (!this.handTool) {
if (options) {
options.set('textLayerMode', pdfDefaultOptions.textLayerMode);
}
this.textLayer = true;
if (this.showFindButton === undefined) {
this.showFindButton = true;
setTimeout(() => {
// todo remove this hack:
const viewFind = document.getElementById('viewFind');
if (viewFind) {
viewFind.classList.remove('invisible');
}
const findbar = document.getElementById('findbar');
if (findbar) {
findbar.classList.remove('invisible');
}
});
}
}
else {
if (options) {
options.set('textLayerMode', this.showHandToolButton ? pdfDefaultOptions.textLayerMode : 0);
}
if (!this.showHandToolButton) {
if (this.showFindButton || this.showFindButton === undefined) {
this.ngZone.run(() => {
this.showFindButton = false;
});
if (this.logLevel >= VerbosityLevel.WARNINGS) {
console.warn(
// tslint:disable-next-line:max-line-length
'Hiding the "find" button because the text layer of the PDF file is not rendered. Use [textLayer]="true" to enable the find button.');
}
}
if (this.showHandToolButton) {
if (this.logLevel >= VerbosityLevel.WARNINGS) {
console.warn(
// tslint:disable-next-line:max-line-length
'Hiding the "hand tool / selection mode" menu because the text layer of the PDF file is not rendered. Use [textLayer]="true" to enable the the menu items.');
this.showHandToolButton = false;
}
}
}
}
}
else {
if (this.textLayer) {
// todo: is this a redundant check?
if (options) {
options.set('textLayerMode', pdfDefaultOptions.textLayerMode);
}
this.textLayer = true;
if (this.showFindButton === undefined) {
this.showFindButton = true;
setTimeout(() => {
// todo remove this hack:
const viewFind = document.getElementById('viewFind');
if (viewFind) {
viewFind.classList.remove('invisible');
}
const findbar = document.getElementById('findbar');
if (findbar) {
findbar.classList.remove('invisible');
}
});
}
}
else {
// todo: is the else branch dead code?
if (options) {
options.set('textLayerMode', 0);
}
this.textLayer = false;
if (this.showFindButton) {
if (this.logLevel >= VerbosityLevel.WARNINGS) {
// tslint:disable-next-line:max-line-length
console.warn('Hiding the "find" button because the text layer of the PDF file is not rendered. Use [textLayer]="true" to enable the find button.');
this.ngZone.run(() => {
this.showFindButton = false;
});
}
}
if (this.showHandToolButton) {
if (this.logLevel >= VerbosityLevel.WARNINGS) {
console.warn(
// tslint:disable-next-line:max-line-length
'Hiding the "hand tool / selection mode" menu because the text layer of the PDF file is not rendered. Use [textLayer]="true" to enable the the menu items.');
this.showHandToolButton = false;
}
}
}
}
}
async overrideDefaultSettings() {
if (typeof window === 'undefined') {
return; // server side rendering
}
const options = window.PDFViewerApplicationOptions;
// tslint:disable-next-line:forin
for (const key in pdfDefaultOptions) {
options.set(key, pdfDefaultOptions[key]);
}
options.set('disablePreferences', true);
await this.setZoom();
options.set('ignoreKeyboard', this.ignoreKeyboard);
options.set('ignoreKeys', this.ignoreKeys);
options.set('acceptKeys', this.acceptKeys);
this.activateTextlayerIfNecessary(options);
if (this.scrollMode || this.scrollMode === ScrollModeType.vertical) {
options.set('scrollModeOnLoad', this.scrollMode);
}
const sidebarVisible = this.sidebarVisible;
const PDFViewerApplication = window.PDFViewerApplication;
if (sidebarVisible !== undefined) {
PDFViewerApplication.sidebarViewOnLoad = sidebarVisible ? 1 : 0;
if (PDFViewerApplication.appConfig) {
PDFViewerApplication.appConfig.sidebarViewOnLoad = sidebarVisible ? this.activeSidebarView : PdfSidebarView.NONE;
}
options.set('sidebarViewOnLoad', this.sidebarVisible ? this.activeSidebarView : 0);
}
if (this.spread === 'even') {
options.set('spreadModeOnLoad', 2);
if (PDFViewerApplication.pdfViewer) {
PDFViewerApplication.pdfViewer.spreadMode = 2;
}
this.onSpreadChange('even');
}
else if (this.spread === 'odd') {
options.set('spreadModeOnLoad', 1);
if (PDFViewerApplication.pdfViewer) {
PDFViewerApplication.pdfViewer.spreadMode = 1;
}
this.onSpreadChange('odd');
}
else {
options.set('spreadModeOnLoad', 0);
if (PDFViewerApplication.pdfViewer) {
PDFViewerApplication.pdfViewer.spreadMode = 0;
}
this.onSpreadChange('off');
}
if (this.printResolution) {
options.set('printResolution', this.printResolution);
}
if (this.showBorders === false) {
options.set('removePageBorders', !this.showBorders);
}
}
openPDF() {
ServiceWorkerOptions.showUnverifiedSignatures = this.showUnverifiedSignatures;
const PDFViewerApplication = window.PDFViewerApplication;
PDFViewerApplication.enablePrint = this.enablePrint;
this.service.ngxExtendedPdfViewerInitialized = true;
if (this._src) {
this.ngxExtendedPdfViewerIncompletelyInitialized = false;
if (!this.list