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,148 lines 359 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 { 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