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,175 lines (1,174 loc) • 376 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 { 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 "./pdf-script-loader.service";
import * as i5 from "./ngx-keyboard-manager.service";
import * as i6 from "./pdf-csp-policy.service";
import * as i7 from "./dynamic-css/dynamic-css.component";
import * as i8 from "./theme/acroform-default-theme/pdf-acroform-default-theme.component";
import * as i9 from "./toolbar/pdf-context-menu/pdf-context-menu.component";
import * as i10 from "./theme/pdf-dark-theme/pdf-dark-theme.component";
import * as i11 from "./pdf-dialog/pdf-alt-text-dialog/pdf-alt-text-dialog.component";
import * as i12 from "./pdf-dialog/pdf-alt-text-settings-dialog/pdf-alt-text-settings-dialog.component";
import * as i13 from "./pdf-dialog/pdf-document-properties-dialog/pdf-document-properties-dialog.component";
import * as i14 from "./pdf-dummy-components/pdf-dummy-components.component";
import * as i15 from "./pdf-dialog/pdf-error-message/pdf-error-message.component";
import * as i16 from "./toolbar/pdf-findbar/pdf-findbar.component";
import * as i17 from "./theme/pdf-light-theme/pdf-light-theme.component";
import * as i18 from "./pdf-dialog/pdf-password-dialog/pdf-password-dialog.component";
import * as i19 from "./pdf-dialog/pdf-prepare-printing-dialog/pdf-prepare-printing-dialog.component";
import * as i20 from "./secondary-toolbar/pdf-secondary-toolbar/pdf-secondary-toolbar.component";
import * as i21 from "./sidebar/pdf-sidebar/pdf-sidebar.component";
import * as i22 from "./toolbar/pdf-toolbar/pdf-toolbar.component";
import * as i23 from "./translate.pipe";
export class NgxExtendedPdfViewerComponent {
platformId;
notificationService;
elementRef;
platformLocation;
cdr;
service;
renderer;
pdfScriptLoaderService;
keyboardManager;
cspPolicyService;
ngZone;
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;
annotationEditorEvent = new EventEmitter();
/* UI templates */
customFindbarInputArea;
customToolbar;
customFindbar;
customFindbarButtons;
customPdfViewer;
customSecondaryToolbar;
customSidebar;
customThumbnail;
customFreeFloatingBar;
showFreeFloatingBar = true;
enableDragAndDrop = true;
forceUsingLegacyES5 = false;
localizationInitialized = false;
resizeObserver;
initialAngularFormData = undefined;
set formData(formData) {
if (this.initialAngularFormData === undefined) {
this.initialAngularFormData = 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))
return;
const hasChanged = this._pageViewMode !== viewMode;
if (!hasChanged)
return;
const mustRedraw = !this.pdfScriptLoaderService.ngxExtendedPdfViewerIncompletelyInitialized && (this._pageViewMode === 'book' || viewMode === 'book');
this._pageViewMode = viewMode;
this.pageViewModeChange.emit(this._pageViewMode);
const PDFViewerApplicationOptions = this.pdfScriptLoaderService.PDFViewerApplicationOptions;
PDFViewerApplicationOptions?.set('pageViewMode', this.pageViewMode);
const PDFViewerApplication = this.pdfScriptLoaderService.PDFViewerApplication;
if (PDFViewerApplication) {
PDFViewerApplication.pdfViewer.pageViewMode = this._pageViewMode;
PDFViewerApplication.findController._pageViewMode = this._pageViewMode;
}
this.handleViewMode(viewMode);
if (mustRedraw) {
this.redrawViewer(viewMode);
}
}
handleViewMode(viewMode) {
switch (viewMode) {
case 'infinite-scroll':
this.handleInfiniteScrollMode();
break;
case 'single':
this.handleSinglePageMode();
break;
case 'book':
this.handleBookMode();
break;
case 'multiple':
this.handleMultiplePageMode();
break;
default:
this.scrollMode = ScrollModeType.vertical;
}
}
handleInfiniteScrollMode() {
if (this.scrollMode === ScrollModeType.page || this.scrollMode === ScrollModeType.horizontal) {
this.scrollMode = ScrollModeType.vertical;
this.pdfScriptLoaderService.PDFViewerApplication.eventBus.dispatch('switchscrollmode', { mode: Number(this.scrollMode) });
}
setTimeout(() => {
// this timeout is necessary because @Input() is called before the child components are initialized
// (and the DynamicCssComponent is a child component)
this.dynamicCSSComponent.removeScrollbarInInfiniteScrollMode(false, this.pageViewMode, this.primaryMenuVisible, this, this.logLevel);
});
}
// since pdf.js, our custom single-page-mode has been replaced by the standard scrollMode="page"
handleSinglePageMode() {
this.scrollMode = ScrollModeType.page;
this._pageViewMode = 'single';
}
handleBookMode() {
this.showBorders = false;
if (this.scrollMode !== ScrollModeType.vertical) {
this.scrollMode = ScrollModeType.vertical;
}
}
handleMultiplePageMode() {
if (this.scrollMode === ScrollModeType.page) {
this.scrollMode = ScrollModeType.vertical;
}
setTimeout(() => {
// this timeout is necessary because @Input() is called before the child components are initialized
// (and the DynamicCssComponent is a child component)
this.dynamicCSSComponent.removeScrollbarInInfiniteScrollMode(true, this.pageViewMode, this.primaryMenuVisible, this, this.logLevel);
});
}
redrawViewer(viewMode) {
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();
}
markForCheck() {
this.cdr.markForCheck();
}
pageViewModeChange = new EventEmitter();
progress = new EventEmitter();
secondaryToolbarComponent;
dynamicCSSComponent;
sidebarComponent;
/* regular attributes */
_src;
srcChange = new EventEmitter();
_scrollMode = ScrollModeType.vertical;
get scrollMode() {
return this._scrollMode;
}
set scrollMode(value) {
if (this._scrollMode !== value) {
const PDFViewerApplication = this.pdfScriptLoaderService.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;
get enablePrintAutoRotate() {
return pdfDefaultOptions.enablePrintAutoRotate;
}
set enablePrintAutoRotate(value) {
pdfDefaultOptions.enablePrintAutoRotate = value;
if (this.pdfScriptLoaderService.PDFViewerApplication?.pdfViewer) {
this.pdfScriptLoaderService.PDFViewerApplication.pdfViewer.enablePrintAutoRotate = value;
}
}
/** Force reloading of the JavaScript code. Useful for testing and micro-frontends */
forceFullReloadOfJavaScriptCode = false;
showTextEditor = 'xxl';
showStampEditor = 'xxl';
showDrawEditor = 'xxl';
showHighlightEditor = 'xxl';
/** How many log messages should be printed?
* Legal values: VerbosityLevel.INFOS (= 5), VerbosityLevel.WARNINGS (= 1), VerbosityLevel.ERRORS (= 0) */
logLevel = VerbosityLevel.WARNINGS;
/** Use the minified (minifiedJSLibraries="true", which is the default) or the user-readable pdf.js library (minifiedJSLibraries="false") */
get minifiedJSLibraries() {
return pdfDefaultOptions._internalFilenameSuffix === '.min';
}
set minifiedJSLibraries(value) {
pdfDefaultOptions._internalFilenameSuffix = value ? '.min' : '';
}
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) {
(async () => {
this.src = await this.convertBlobToUint8Array(url);
if (this.service.ngxExtendedPdfViewerInitialized) {
if (this.pdfScriptLoaderService.ngxExtendedPdfViewerIncompletelyInitialized) {
this.openPDF();
}
else {
(async () => this.openPDF2())();
}
// else openPDF is called later, so we do nothing to prevent loading the PDF file twice
}
})();
}
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;
}
}
async convertBlobToUint8Array(blob) {
// first try the algorithm for modern browsers and node.js
if (blob.arrayBuffer) {
const arrayBuffer = await blob.arrayBuffer();
return new Uint8Array(arrayBuffer);
}
// then try the old-fashioned way
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => {
if (reader.result) {
resolve(new Uint8Array(reader.result));
}
else {
reject(new Error('Error converting Blob to Uint8Array'));
}
};
reader.onerror = () => {
reject(new Error('FileReader error'));
};
reader.readAsArrayBuffer(blob);
});
}
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. checkHeight() 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 timeout is necessary because @Input() is called before the child components are initialized
// (and the DynamicCssComponent is a child component)
this.dynamicCSSComponent.checkHeight(this, this.logLevel);
});
}
get height() {
return this._height;
}
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 = [];
hasTextLayer = true;
/** 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 = typeof window === 'undefined' ? 'en' : navigator.language;
/** 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;
originalPrint = typeof window !== 'undefined' ? window.print : undefined;
_showSidebarButton = true;
useInlineScripts = 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 = this.pdfScriptLoaderService.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;
showFindMultiple = true;
showFindRegexp = false;
showFindEntireWord = true;
showFindMatchDiacritics = true;
showFindResultsCount = true;
showFindMessages = true;
showPagingButtons = true;
showFirstAndLastPageButtons = true;
showPreviousAndNextPageButtons = true;
showPageNumber = true;
showPageLabel = true;
showZoomButtons = true;
showZoomDropdown = 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;
set showRotateButton(visibility) {
this.showRotateCwButton = visibility;
this.showRotateCcwButton = visibility;
}
showRotateCwButton = true;
showRotateCcwButton = true;
_handTool = !this.isIOS();
set handTool(handTool) {
if (this.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;
showSpreadButton = true;
showPropertiesButton = true;
showBorders = true;
spread;
set showScrollingButtons(show) {
this.showVerticalScrollButton = show;
this.showHorizontalScrollButton = show;
this.showWrappedScrollButton = show;
this.showInfiniteScrollButton = show;
this.showBookModeButton = show;
this.showSinglePageModeButton = show;
}
spreadChange = new EventEmitter();
thumbnailDrawn = new EventEmitter();
_page = undefined;
get page() {
return this._page;
}
set page(newPageNumber) {
if (newPageNumber) {
// silently cope with strings
this._page = Number(newPageNumber);
}
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];
get zoomLevels() {
if (this.maxZoom && this.maxZoom === this.minZoom) {
return [this.maxZoom];
}
return this._zoomLevels;
}
set zoomLevels(value) {
this._zoomLevels = value;
}
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;
}
secondaryToolbarTop = undefined;
sidebarPositionTop = undefined;
// dirty IE11 hack - temporary solution
findbarTop = undefined;
// dirty IE11 hack - temporary solution
findbarLeft = undefined;
initializationPromise = null;
checkRootElementTimeout;
destroyInitialization = false;
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());
}
serverSideRendering = true;
/**
* Checks if the code is running in a browser environment.
*/
isBrowser() {
return typeof window !== 'undefined' && typeof document !== 'undefined';
}
calcViewerPositionTop() {
if (!this.isBrowser()) {
return;
}
if (this.toolbar === undefined) {
this.sidebarPositionTop = '0';
return;
}
const 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(platformId, notificationService, elementRef, platformLocation, cdr, service, renderer, pdfScriptLoaderService, keyboardManager, cspPolicyService, ngZone) {
this.platformId = platformId;
this.notificationService = notificationService;
this.elementRef = elementRef;
this.platformLocation = platformLocation;
this.cdr = cdr;
this.service = service;
this.renderer = renderer;
this.pdfScriptLoaderService = pdfScriptLoaderService;
this.keyboardManager = keyboardManager;
this.cspPolicyService = cspPolicyService;
this.ngZone = ngZone;
this.baseHref = this.platformLocation.getBaseHrefFromDOM();
if (isPlatformBrowser(this.platformId)) {
this.serverSideRendering = false;
this.toolbarWidth = String(document.body.clientWidth);
}
}
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));
}
reportSourceChanges(change) {
this._src = change.sourcefile;
this.srcChangeTriggeredByUser = true;
this.srcChange.emit(change.sourcefile);
const PDFViewerApplication = this.pdfScriptLoaderService.PDFViewerApplication;
if (this.filenameForDownload) {
PDFViewerApplication.appConfig.filenameForDownload = this.filenameForDownload;
}
else {
PDFViewerApplication.appConfig.filenameForDownload = this.guessFilenameFromUrl(this._src);
}
}
async ngOnInit() {
this.hideToolbarIfItIsEmpty();
if (isPlatformBrowser(this.platformId)) {
this.ngZone.runOutsideAngular(() => {
this.initializationPromise = this.initialize;
this.initializationPromise();
});
}
}
async initialize() {
try {
await this.waitForRootElement();
if (this.destroyInitialization)
return;
if (isPlatformBrowser(this.platformId)) {
this.addTranslationsUnlessProvidedByTheUser();
await this.waitUntilOldComponentIsGone();
if (this.destroyInitialization)
return;
await this.pdfScriptLoaderService.ensurePdfJsHasBeenLoaded(this.useInlineScripts, this.forceUsingLegacyES5, this.forceFullReloadOfJavaScriptCode);
if (this.destroyInitialization)
return;
if (this.formSupport) {
this.formSupport.registerFormSupportWithPdfjs(this.pdfScriptLoaderService.PDFViewerApplication);
this.keyboardManager.registerKeyboardListener(this.pdfScriptLoaderService.PDFViewerApplication);
this.formSupport.ngZone = this.ngZone;
this.formSupport.cdr = this.cdr;
}
this.pdfScriptLoaderService.PDFViewerApplication.cspPolicyService = this.cspPolicyService;
this.ngZone.runOutsideAngular(() => this.doInitPDFViewer());
}
}
catch (error) {
console.error('Initialization failed:', error);
}
}
async waitForRootElement() {
return new Promise((resolve, reject) => {
const checkRootElement = () => {
if (this.destroyInitialization) {
reject(new Error('Component destroyed'));
return;
}
if (this.root && this.root.nativeElement && this.root.nativeElement.offsetParent !== null) {
resolve();
}
else {
this.checkRootElementTimeout = setTimeout(checkRootElement, 50);
}
};
checkRootElement();
});
}
async waitUntilOldComponentIsGone() {
return new Promise((resolve) => {
const interval = setInterval(() => {
if (!this.service.ngxExtendedPdfViewerInitialized) {
clearInterval(interval);
resolve();
}
}, 10);
});
}
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) {
const classesToRemove = [
'hidden',
'invisible',
'hiddenXXLView',
'hiddenXLView',
'hiddenLargeView',
'hiddenMediumView',
'hiddenSmallView',
'hiddenTinyView',
'visibleXXLView',
'visibleXLView',
'visibleLargeView',
'visibleMediumView',
'visibleSmallView',
'visibleTinyView',
];
root.classList.remove(...classesToRemove);
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;
}
afterPrintListener = () => {
this.afterPrint.emit();
};
beforePrintListener = () => {
this.beforePrint.emit();
};
guessFilenameFromUrl(src) {
if (src && typeof src === 'string') {
const slash = src.lastIndexOf('/');
if (slash > 0) {
return src.substring(slash + 1);
}
else {
return src;
}
}
return undefined;
}
doInitPDFViewer() {
if (typeof window === 'undefined') {
// server-side rendering
return;
}
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.");
}
this.overrideDefaultSettings();
const onLoaded = () => {
if (!this.pdfScriptLoaderService.PDFViewerApplication.eventBus) {
console.error("Eventbus is null? Let's try again.");
setTimeout(() => {
onLoaded();
}, 10);
}
else {
this.pdfScriptLoaderService.PDFViewerApplication.eventBus.on('sourcechanged', this.reportSourceChanges.bind(this));
this.pdfScriptLoaderService.PDFViewerApplication.eventBus.on('afterprint', this.afterPrintListener);
this.pdfScriptLoaderService.PDFViewerApplication.eventBus.on('beforeprint', this.beforePrintListener);
this.localizationInitialized = true;
if (!this.pdfScriptLoaderService.shuttingDown) {
// hurried users sometimes reload the PDF before it has finished initializing
this.calcViewerPositionTop();
this.afterLibraryInit();
this.openPDF();
this.assignTabindexes();
if (this.replaceBrowserPrint) {
this.doReplaceBrowserPrint(this.replaceBrowserPrint);
}
}
}
};
document.addEventListener('webviewerinitialized', onLoaded, { once: true });
this.activateTextlayerIfNecessary(null);
setTimeout(() => {
if (!this.pdfScriptLoaderService.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.initResizeObserver();
this.onResize();
this.hideToolbarIfItIsEmpty();
this.dummyComponents.addMissingStandardWidgets();
if (this.pdfScriptLoaderService.PDFViewerApplicationOptions) {
const PDFViewerApplicationOptions = this.pdfScriptLoaderService.PDFViewerApplicationOptions;
globalThis.PDFViewerApplicationOptions = PDFViewerApplicationOptions;
}
this.pdfScriptLoaderService.webViewerLoad(this.cspPolicyService);
const PDFViewerApplication = this.pdfScriptLoaderService.PDFViewerApplication;
PDFViewerApplication.appConfig.defaultUrl = ''; // IE bugfix
const PDFViewerApplicationOptions = this.pdfScriptLoaderService.PDFViewerApplicationOptions;
PDFViewerApplicationOptions.set('enableDragAndDrop', this.enableDragAndDrop);
PDFViewerApplicationOptions.set('localeProperties', { lang: this.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);
if (this.theme === 'dark') {
PDFViewerApplicationOptions.set('viewerCssTheme', 2);
}
else if (this.theme === 'light') {
PDFViewerApplicationOptions.set('viewerCssTheme', 1);
}
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() {
queueMicrotask(() => this.notificationService.onPDFJSInitSignal.set(this.pdfScriptLoaderService.PDFViewerApplication));
}
onSpreadChange(newSpread) {
this.spreadChange.emit(newSpread);
}
toggleVisibility = (elementId, cssClass = 'invisible') => {
const element = document.getElementById(elementId);
element?.classList.remove(cssClass);
};
activateTextlayerIfNecessary(options) {
const setTextLayerMode = (mode) => {
options?.set('textLayerMode', mode);
this.pdfScriptLoaderService.PDFViewerApplication.pdfViewer?.setTextLayerMode(mode);
};
if (this.textLayer === undefined) {
if (!this.handTool) {
setTextLayerMode(pdfDefaultOptions.textLayerMode);
this.textLayer = true;
if (this.showFindButton === undefined) {
this.showFindButton = true;
setTimeout(() => {
this.toggleVisibility('viewFind');
this.toggleVisibility('findbar');
});
}
}
else {
setTextLayerMode(this.showHandToolButton ? pdfDefaultOptions.textLayerMode : 0);
if (!this.showHandToolButton) {
if (this.showFindButton || this.showFindButton === undefined) {
queueMicrotask(() => {
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 {
setTextLayerMode(pdfDefaultOptions.textLayerMode);
this.textLayer = true;
if (this.showFindButton === undefined) {
this.showFindButton = true;
setTimeout(() => {
this.toggleVisibility('viewFind');
this.toggleVisibility('findbar');
});
}
}
}
async overrideDefaultSettings() {
if (typeof window === 'undefined') {
return; // server side rendering
}
const options = this.pdfScriptLoaderService.PDFViewerApplicationOptions;
// tslint:disable-next-line:forin
const optionsToIgnore = [
'needsES5',
'rangeChunkSize',
'_internalFilenameSuffix',
'assetsFolder',
'doubleTapZoomFactor',
'doubleTapZoomsInHandMode',
'doubleTapZoomsInTextSelectionMode',
'doubleTapResetsZoomOnSecondDoubleTap',
];
for (const key in pdfDefaultOptions) {
if (!optionsToIgnore.includes(key)) {
const option = pdfDefaultOptions[key];
if (key !== 'findController' && typeof option === 'function') {
options.set(key, option());
}
else {
options.set(key, pdfDefaultOptions[key]);
}
}
}
options.set('disablePreferences', true);
await this.setZoom();
this.keyboardManager.ignoreKeyboard = this.ignoreKeyboard;
this.keyboardManager.ignoreKeys = this.ignoreKeys;
this.keyboardManager.acceptKeys = this.acceptKeys;
this.activateTextlayerIfNecessary(options);
if (this.scrollMode || this.scrollMode === ScrollModeType.vertical) {
options.set('scrollModeOnLoad', this.scrollMode);
}
const sidebarVisible = this.sidebarVisible;
const PDFViewerApplication = this.pdfScriptLoaderService.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);
}
const PDFViewerApplicationOptions = this.pdfScriptLoaderService.PDFViewerApplicationOptions;
PDFViewerApplicationOptions.set('localeProperties', { lang: this.language });
}
async openPDF() {
const PDFViewerApplication = this.pdfScriptLoaderService.PDFViewerApplication;
PDFViewerApplication.serviceWorkerOptions.showUnverifiedSignatures = this.showUnverifiedSignatures;
PDFViewerApplication.enablePrint = this.enablePrint;
if (this.filenameForDownload) {
PDFViewerApplication.appConfig.filenameForDownload = this.filenameForDownload;
}
else {
PDFViewerApplication.appConfig.filenameForDownload = this.guessFilenameFromUrl(this._src);
}
this.service.ngxExtendedPdfViewerInitialized = true;
this.registerEventListeners(PDFViewerApplication);
this.selectCursorTool();
if (!this.listenToURL) {
PDFViewerApplication.pdfLinkService.setHash = undefined;
}
if (this._src) {
this.pdfScriptLoaderService.ngxExtendedPdfViewerIncompletelyInitialized = false;
setTimeout(async () => this.dynamicCSSComponent.checkHeight(this, this.logLevel), 100);
// open a file in the viewer
if (!!this._src) {
let workerSrc = pdfDefaultOptions.workerSrc;
if (typeof workerSrc === 'function') {
workerSrc = workerSrc();
}
const options = {
password: this.password,
verbosity: this.logLevel,
workerSrc,
};
if (this._src['range']) {
options.range = this._src['range'];
}
if (this.httpHeaders) {
options.httpHeaders = this.httpHeaders;
}
if (this.authorization) {
options.withCredentials = true;
if (typeof this.authorization != 'boolean') {
if (!options.httpHeaders)
options.httpHeaders = {};
options.httpHeaders.Authorization = this.authorization;
}
}
options.baseHref = this.baseHref;
PDFViewerApplication.onError = (error) => this.pdfLoadingFailed.emit(error);
if (typeof this._src === 'string') {
options.url = this._src;
}
else if (this._src instanceof ArrayBuffer) {
options.data = this._src;
}
else if (this._src instanceof Uint8Array) {
options.data = this._src;
}
options.rangeChunkSize = pdfDefaultOptions.rangeChunkSize;
options.cspPolicyService = this.cspPolicyService;
PDFViewerApplication.findBar?.close();
PDFViewerApplication.secondaryToolbar?.close();
PDFViewerApplication.eventBus.dispatch('annotationeditormodechanged', { mode: 0 });
await PDFViewerApplication.open(options);
this.pdfLoadingStarts.emit({});
setTimeout(async () => this.setZoom());
}
setTimeout(() => {
if (!this.pdfScriptLoaderService.shuttingDown) {
// hurried users sometimes reload the PDF before it has finished initializing
if (this.page) {
PDFViewerApplication.page = Number(this.page);
}
}
}, 100);
}
}
registerEventListeners(PDFViewerApplication) {
PDFViewerApplication.eventBus.on('annotation-editor-event', (x) => {
queueMicrotask(() => {
this.annotationEditorEvent.emit(x);
});
});
PDFViewerApplication.eventBus.on('toggleSidebar', (x) => {
queueMicrotask(() => {
this.sidebarVisible = x.visible;