UNPKG

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
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;