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,192 lines (1,180 loc) 1.11 MB
import * as i1 from '@angular/common'; import { isPlatformBrowser, DOCUMENT, CommonModule } from '@angular/common'; import * as i0 from '@angular/core'; import { Component, EventEmitter, signal, effect, Injectable, CSP_NONCE, Inject, Pipe, PLATFORM_ID, Input, Optional, ViewEncapsulation, HostListener, Output, TemplateRef, ViewChild, ContentChild, ChangeDetectionStrategy, NgModule } from '@angular/core'; import * as i2 from '@angular/platform-browser'; import * as i1$1 from '@angular/forms'; import { FormsModule } from '@angular/forms'; var FindState; (function (FindState) { FindState[FindState["FOUND"] = 0] = "FOUND"; FindState[FindState["NOT_FOUND"] = 1] = "NOT_FOUND"; FindState[FindState["WRAPPED"] = 2] = "WRAPPED"; FindState[FindState["PENDING"] = 3] = "PENDING"; })(FindState || (FindState = {})); var PdfCursorTools; (function (PdfCursorTools) { PdfCursorTools[PdfCursorTools["SELECT"] = 0] = "SELECT"; PdfCursorTools[PdfCursorTools["HAND"] = 1] = "HAND"; PdfCursorTools[PdfCursorTools["ZOOM"] = 2] = "ZOOM"; })(PdfCursorTools || (PdfCursorTools = {})); var AnnotationMode; (function (AnnotationMode) { AnnotationMode[AnnotationMode["DISABLE"] = 0] = "DISABLE"; AnnotationMode[AnnotationMode["ENABLE"] = 1] = "ENABLE"; AnnotationMode[AnnotationMode["ENABLE_FORMS"] = 2] = "ENABLE_FORMS"; AnnotationMode[AnnotationMode["ENABLE_STORAGE"] = 3] = "ENABLE_STORAGE"; })(AnnotationMode || (AnnotationMode = {})); ; var AnnotationEditorType; (function (AnnotationEditorType) { AnnotationEditorType[AnnotationEditorType["DISABLE"] = -1] = "DISABLE"; AnnotationEditorType[AnnotationEditorType["NONE"] = 0] = "NONE"; AnnotationEditorType[AnnotationEditorType["FREETEXT"] = 3] = "FREETEXT"; AnnotationEditorType[AnnotationEditorType["HIGHLIGHT"] = 9] = "HIGHLIGHT"; AnnotationEditorType[AnnotationEditorType["STAMP"] = 13] = "STAMP"; AnnotationEditorType[AnnotationEditorType["INK"] = 15] = "INK"; })(AnnotationEditorType || (AnnotationEditorType = {})); const AnnotationEditorParamsType = { RESIZE: 1, CREATE: 2, FREETEXT_SIZE: 11, FREETEXT_COLOR: 12, FREETEXT_OPACITY: 13, INK_COLOR: 21, INK_THICKNESS: 22, INK_OPACITY: 23, HIGHLIGHT_COLOR: 31, HIGHLIGHT_DEFAULT_COLOR: 32, HIGHLIGHT_THICKNESS: 33, HIGHLIGHT_FREE: 34, HIGHLIGHT_SHOW_ALL: 35, }; const _isIE11 = typeof window === 'undefined' ? false : !!globalThis.MSInputMethodContext && !!document.documentMode; const isEdge = typeof navigator === 'undefined' || /Edge\/\d./i.test(navigator.userAgent); const needsES5 = typeof ReadableStream === 'undefined' || typeof Promise['allSettled'] === 'undefined'; const pdfjsVersion = '4.10.708'; const pdfjsBleedingEdgeVersion = '4.10.708'; function getVersionSuffix(folder) { if (folder?.includes('bleeding-edge')) { return pdfjsBleedingEdgeVersion; } return pdfjsVersion; } function assetsUrl(url, postfixIfPathIsRelativ = '') { if (url.includes('://')) { // the assets folder is on an absolute path (like https://example.com/assets) return url; } return `./${url + postfixIfPathIsRelativ}`; } function getSafeCanvasSize() { if (typeof window === 'undefined' || typeof document === 'undefined') { return 4096; } // Create a temporary WebGL context const canvas = document.createElement('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); let maxTextureSize; if (gl instanceof WebGLRenderingContext) { maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); } else { maxTextureSize = 4096; } // Get available device RAM (in MB) function getAvailableMemoryMB() { if ('deviceMemory' in navigator) { return navigator.deviceMemory * 1024; // Convert GB to MB } if (window.performance && 'memory' in window.performance) { return window.performance.memory.jsHeapSizeLimit / 1024 / 1024; // Only works on Chrome, Firefox, and Edgewindow.performance.memory.jsHeapSizeLimit / 1024 / 1024; // Only works on Chrome } return 4096; // Default to 4GB if unknown } const availableMemoryMB = getAvailableMemoryMB(); // Conservative formula: Scale by square root of available memory let estimatedSafeSize = Math.floor(Math.sqrt((availableMemoryMB * 1024 * 1024) / 6)); // Apply platform-specific limits const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !('MSStream' in window); const isMobile = /Android|iPhone|iPad|iPod/.test(navigator.userAgent); const isHighEndDesktop = availableMemoryMB > 12000; // Assume high-end desktops have >12GB RAM if (isIOS) { estimatedSafeSize = Math.min(estimatedSafeSize, 4096); // iOS Safari memory limits } else if (isMobile) { estimatedSafeSize = Math.min(estimatedSafeSize, 4096); // Most mobile devices } else if (isHighEndDesktop) { estimatedSafeSize = Math.min(estimatedSafeSize, 8192); // Allow larger sizes for desktops } else { estimatedSafeSize = Math.min(estimatedSafeSize, 6000); // Mid-range desktops } // Final limit based on GPU and estimated memory safety const maxWidth = Math.min(maxTextureSize, estimatedSafeSize); return maxWidth * maxWidth; } // sonar ignore next line const pdfDefaultOptions = { needsES5: _isIE11 || isEdge || needsES5, annotationEditorMode: 0, annotationMode: AnnotationMode.ENABLE_FORMS, defaultZoomDelay: 400, cursorToolOnLoad: 0, defaultUrl: '', defaultZoomValue: '', disableHistory: false, disablePageLabels: false, enablePermissions: false, docBaseUrl: '', enablePrintAutoRotate: true, externalLinkRel: 'noopener noreferrer nofollow', externalLinkTarget: 0, findController: undefined, historyUpdateUrl: false, ignoreDestinationZoom: false, imageResourcesPath: './images/', maxCanvasPixels: getSafeCanvasSize(), forcePageColors: false, pageColorsBackground: 'Canvas', pageColorsForeground: 'CanvasText', pdfBugEnabled: false, printResolution: 150, rangeChunkSize: 65536, removePageBorders: false, enableXfa: true, fontExtraProperties: false, sidebarViewOnLoad: -1, scrollModeOnLoad: -1, spreadModeOnLoad: -1, textLayerMode: 1, // viewerCssTheme: 0, // not supported by ngx-extended-pdf-viewer, use [theme] instead viewOnLoad: 0, cMapPacked: true, cMapUrl: function () { return `${assetsUrl(pdfDefaultOptions.assetsFolder, '/..')}/cmaps/`; }, disableAutoFetch: false, disableFontFace: false, disableRange: false, disableStream: true, isEvalSupported: true, isOffscreenCanvasSupported: true, maxImageSize: -1, pdfBug: false, verbosity: 1, workerPort: null, assetsFolder: 'assets', _internalFilenameSuffix: '.min', sandboxBundleSrc: function () { return pdfDefaultOptions.needsES5 ? `./pdf.sandbox-${getVersionSuffix(assetsUrl(pdfDefaultOptions.assetsFolder))}-es5.mjs` : `./pdf.sandbox-${getVersionSuffix(assetsUrl(pdfDefaultOptions.assetsFolder))}${pdfDefaultOptions._internalFilenameSuffix}.mjs`; }, workerSrc: function () { return pdfDefaultOptions.needsES5 ? `${assetsUrl(pdfDefaultOptions.assetsFolder)}/pdf.worker-${getVersionSuffix(assetsUrl(pdfDefaultOptions.assetsFolder))}-es5.mjs` : `${assetsUrl(pdfDefaultOptions.assetsFolder)}/pdf.worker-${getVersionSuffix(assetsUrl(pdfDefaultOptions.assetsFolder))}${pdfDefaultOptions._internalFilenameSuffix}.mjs`; }, standardFontDataUrl: () => `${assetsUrl(pdfDefaultOptions.assetsFolder, '/..')}/standard_fonts/`, // options specific to ngx-extended-pdf-viewer (as opposed to being used by pdf.js) doubleTapZoomFactor: 'page-width', doubleTapZoomsInHandMode: true, doubleTapZoomsInTextSelectionMode: false, doubleTapResetsZoomOnSecondDoubleTap: false, enableScripting: true, defaultCacheSize: 50, passwordPrompt: undefined, enableHWA: true, // enable hardware acceleration. Active since pdf.js 4.4. }; var ScrollModeType; (function (ScrollModeType) { ScrollModeType[ScrollModeType["vertical"] = 0] = "vertical"; ScrollModeType[ScrollModeType["horizontal"] = 1] = "horizontal"; ScrollModeType[ScrollModeType["wrapped"] = 2] = "wrapped"; ScrollModeType[ScrollModeType["page"] = 3] = "page"; })(ScrollModeType || (ScrollModeType = {})); var SpreadModeType; (function (SpreadModeType) { SpreadModeType[SpreadModeType["UNKNOWN"] = -1] = "UNKNOWN"; SpreadModeType[SpreadModeType["NONE"] = 0] = "NONE"; SpreadModeType[SpreadModeType["ODD"] = 1] = "ODD"; SpreadModeType[SpreadModeType["EVEN"] = 2] = "EVEN"; })(SpreadModeType || (SpreadModeType = {})); var VerbosityLevel; (function (VerbosityLevel) { VerbosityLevel[VerbosityLevel["ERRORS"] = 0] = "ERRORS"; VerbosityLevel[VerbosityLevel["WARNINGS"] = 1] = "WARNINGS"; VerbosityLevel[VerbosityLevel["INFOS"] = 5] = "INFOS"; })(VerbosityLevel || (VerbosityLevel = {})); /** List of all fields that can be customized */ const requiredIds = [ 'attachmentsView', 'authorField', 'contextFirstPage', 'contextLastPage', 'contextPageRotateCcw', 'contextPageRotateCw', 'creationDateField', 'creatorField', 'currentOutlineItem', 'cursorHandTool', 'cursorSelectTool', 'customScaleOption', 'documentProperties', 'documentPropertiesClose', 'download', 'primaryEditorFreeText', 'primaryEditorHighlight', 'primaryEditorInk', 'primaryEditorStamp', 'editorModeButtons', 'editorNone', 'editorStampAddImage', 'errorClose', 'errorMessage', 'errorMoreInfo', 'errorShowLess', 'errorShowMore', 'errorWrapper', 'fileNameField', 'fileSizeField', 'findbar', 'findCurrentPage', 'findEntireWord', 'findFuzzy', 'findHighlightAll', 'findIgnoreAccents', 'findInput', 'findInputMultiline', 'findMatchCase', 'findMatchDiacritics', 'findMsg', 'findMultipleSearchTexts', 'findNext', 'findPrevious', 'findRange', 'findResultsCount', 'firstPage', 'individualWordsMode', 'individualWordsModeLabel', 'keywordsField', 'lastPage', 'linearizedField', 'modificationDateField', 'next', 'numPages', 'openFile', 'outerContainer', 'outerContainer', 'outlineOptionsContainer', 'outlineView', 'pageCountField', 'pageNumber', 'pageRotateCcw', 'pageRotateCw', 'pageSizeField', 'password', 'passwordCancel', 'passwordSubmit', 'passwordText', 'presentationMode', 'previous', 'printButton', 'producerField', 'scaleSelect', 'scaleSelectContainer', 'scrollHorizontal', 'scrollPage', 'scrollVertical', 'scrollWrapped', 'secondaryDownload', 'secondaryOpenFile', 'secondaryPresentationMode', 'secondaryPrintButton', 'secondaryToolbar', 'secondaryToolbarButtonContainer', 'secondaryToolbarToggle', 'secondaryViewBookmark', 'sidebarResizer', 'primarySidebarToggle', 'spreadEven', 'spreadNone', 'spreadOdd', 'subjectField', 'thumbnailView', 'titleField', 'toolbarViewer', 'versionField', 'viewAttachments', 'viewAttachments', 'viewBookmark', 'viewerContainer', 'viewFind', 'viewLayers', 'viewOutline', 'viewOutline', 'viewThumbnail', 'viewThumbnail', 'primaryZoomIn', 'primaryZoomOut', ]; class PdfDummyComponentsComponent { dummyComponentsContainer; addMissingStandardWidgets() { this.dummyComponentsContainer = document.getElementsByClassName('dummy-pdf-viewer-components')[0]; const container = this.dummyComponentsContainer; if (!container) { return; } for (let i = 0; i < container.children.length; i++) { const child = container.firstChild; if (child) { container.removeChild(child); } } requiredIds.forEach((id) => { if (this.needsDummyWidget(id)) { const dummy = document.createElement('span'); dummy.id = id; dummy.className = 'invisible dummy-component'; this.dummyComponentsContainer.appendChild(dummy); } }); if (this.needsDummyWidget('scaleSelect')) { const dummy = document.createElement('select'); dummy.id = 'scaleSelect'; dummy.className = 'invisible dummy-component'; this.dummyComponentsContainer.appendChild(dummy); } } needsDummyWidget(id) { const widget = document.getElementById(id); if (!widget) { return true; } return false; } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PdfDummyComponentsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "17.3.12", type: PdfDummyComponentsComponent, selector: "pdf-dummy-components", ngImport: i0, template: "<span class=\"invisible dummy-pdf-viewer-components\">\n</span>\n" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PdfDummyComponentsComponent, decorators: [{ type: Component, args: [{ selector: 'pdf-dummy-components', template: "<span class=\"invisible dummy-pdf-viewer-components\">\n</span>\n" }] }] }); class NgxFormSupport { /** Maps the internal ids of the annotations of pdf.js to their field name */ formIdToFullFieldName = {}; formIdToField = {}; radioButtons = {}; formData = {}; initialFormDataStoredInThePDF = {}; formDataChange = new EventEmitter(); PDFViewerApplication; ngZone; // set during the initializaion of the PDF viewer cdr; // set during the initializaion of the PDF viewer reset() { this.formData = {}; this.formIdToFullFieldName = {}; } registerFormSupportWithPdfjs(PDFViewerApplication) { this.PDFViewerApplication = PDFViewerApplication; globalThis.getFormValueFromAngular = (key) => this.getFormValueFromAngular(key); globalThis.updateAngularFormValue = (key, value) => this.updateAngularFormValueCalledByPdfjs(key, value); globalThis.registerAcroformField = (id, element, value, radioButtonValueName, initialValueFromPDF) => this.registerAcroformField(id, element, value, radioButtonValueName, initialValueFromPDF); globalThis.registerXFAField = (element, value, initialValueFromPDF) => this.registerXFAField(element, value, initialValueFromPDF); } registerAcroformField(id, element, value, radioButtonValueName, initialFormValueFromPDF) { const fieldName = element.name; this.formIdToField[id] = element; this.formIdToFullFieldName[id] = fieldName; if (element instanceof HTMLInputElement && element.type === 'radio') { const groupName = fieldName; this.formIdToFullFieldName[id] = groupName; if (value) { this.formData[groupName] = radioButtonValueName; this.initialFormDataStoredInThePDF[groupName] = initialFormValueFromPDF; } element.setAttribute('exportValue', radioButtonValueName); if (!this.radioButtons[groupName]) { this.radioButtons[groupName] = []; } this.radioButtons[groupName].push(element); } else if (element instanceof HTMLSelectElement) { this.formData[fieldName] = this.getValueOfASelectField(element); this.initialFormDataStoredInThePDF[fieldName] = initialFormValueFromPDF; } else { if (value !== undefined) { this.formData[fieldName] = value; } this.initialFormDataStoredInThePDF[fieldName] = initialFormValueFromPDF; } } registerXFAField(element, value, initialFormValueFromPDF) { const fullFieldName = this.findFullXFAName(element); if (element instanceof HTMLInputElement && element.type === 'radio') { const id = element.getAttribute('fieldid') ?? ''; // remove the xfa name of the radio button itself form the field name, // because the field name refers to the entire group of relatated radio buttons const groupName = fullFieldName.substring(0, fullFieldName.lastIndexOf('.')); this.formIdToFullFieldName[id] = groupName; this.formData[groupName] = value?.value; this.initialFormDataStoredInThePDF[groupName] = initialFormValueFromPDF; if (!this.radioButtons[groupName]) { this.radioButtons[groupName] = []; } this.radioButtons[groupName].push(element); } else if (element instanceof HTMLInputElement) { const id = element.getAttribute('fieldid') ?? ''; this.formIdToField[id] = element; this.formIdToFullFieldName[id] = fullFieldName; this.formData[fullFieldName] = value?.value; this.initialFormDataStoredInThePDF[fullFieldName] = initialFormValueFromPDF; } else if (element instanceof HTMLSelectElement) { const id = element.getAttribute('fieldid') ?? ''; this.formIdToField[id] = element; this.formIdToFullFieldName[id] = fullFieldName; this.formData[fullFieldName] = value?.value; this.initialFormDataStoredInThePDF[fullFieldName] = initialFormValueFromPDF; } else if (element instanceof HTMLTextAreaElement) { const id = element.getAttribute('fieldid') ?? ''; this.formIdToField[id] = element; this.formIdToFullFieldName[id] = fullFieldName; this.formData[fullFieldName] = value?.value; this.initialFormDataStoredInThePDF[fullFieldName] = initialFormValueFromPDF; } else { console.error("Couldn't register an XFA form field", element); } } getValueOfASelectField(selectElement) { const { options, multiple } = selectElement; if (!multiple) { return options.selectedIndex === -1 ? null : options[options.selectedIndex]['value']; } return Array.prototype.filter.call(options, (option) => option.selected).map((option) => option['value']); } getFormValueFromAngular(element) { let key; if (element instanceof HTMLElement) { const fieldName = this.findXFAName(element); if (fieldName) { if (this.formData.hasOwnProperty(fieldName)) { key = fieldName; } else { key = this.findFullXFAName(element); } } else { console.error("Couldn't find the field name or XFA name of the form field", element); return { value: null }; } } else { key = element; } return { value: this.formData[key] }; } findXFAName(element) { let parentElement = element; while (!parentElement.getAttribute('xfaname') && parentElement.parentElement) { parentElement = parentElement.parentElement; } if (element instanceof HTMLInputElement && element.type === 'radio') { do { parentElement = parentElement?.parentElement; } while (!parentElement?.getAttribute('xfaname') && parentElement); } let fieldName = parentElement?.getAttribute('xfaname'); if (!fieldName) { throw new Error("Couldn't find the xfaname of the field"); } return fieldName; } findFullXFAName(element) { let parentElement = element; let fieldName = ''; while (parentElement instanceof HTMLElement && parentElement.parentElement) { const xfaName = parentElement.getAttribute('xfaname'); if (xfaName) { fieldName = xfaName + '.' + fieldName; } parentElement = parentElement.parentElement; } if (!fieldName) { throw new Error("Couldn't find the xfaname of the field"); } fieldName = fieldName.substring(0, fieldName.length - 1); if (element instanceof HTMLInputElement && element.type === 'radio') { // ignore the last part of the xfaName because it's actually the value of the field return fieldName.substring(0, fieldName.lastIndexOf('.')); } return fieldName; } updateAngularFormValueCalledByPdfjs(key, value) { if (!this.formData) { this.formData = {}; } if (typeof key === 'string') { const acroFormKey = this.formIdToFullFieldName[key]; const fullKey = acroFormKey ?? Object.values(this.formIdToFullFieldName).find((k) => k === key || k.endsWith('.' + key)); if (fullKey) { const field = this.formIdToField[key]; let change = this.doUpdateAngularFormValue(field, value, fullKey); if (change) { this.ngZone.run(() => { this.formDataChange.emit(this.formData); this.cdr.detectChanges(); }); } } else { console.error("Couldn't find the field with the name " + key); } } else { let change = false; const shortFieldName = this.findXFAName(key); if (this.formData.hasOwnProperty(shortFieldName)) { change = this.doUpdateAngularFormValue(key, value, shortFieldName); } const fullFieldName = this.findFullXFAName(key); if (fullFieldName !== shortFieldName) { change ||= this.doUpdateAngularFormValue(key, value, fullFieldName); } if (change) { this.ngZone.run(() => { this.formDataChange.emit(this.formData); this.cdr.detectChanges(); }); } } } doUpdateAngularFormValue(field, value, fullKey) { let change = false; if (field instanceof HTMLInputElement && field.type === 'checkbox') { const exportValue = field.getAttribute('exportvalue'); if (exportValue) { if (value.value) { if (this.formData[fullKey] !== exportValue) { this.formData[fullKey] = exportValue; change = true; } } else { if (this.formData[fullKey] !== false) { this.formData[fullKey] = false; change = true; } } } else if (this.formData[fullKey] !== value.value) { this.formData[fullKey] = value.value; change = true; } } else if (field instanceof HTMLInputElement && field.type === 'radio') { const exportValue = field.getAttribute('exportvalue') ?? field.getAttribute('xfaon'); if (value.value) { if (this.formData[fullKey] !== exportValue) { this.formData[fullKey] = exportValue; change = true; } } } else if (this.formData[fullKey] !== value.value) { this.formData[fullKey] = value.value; change = true; } return change; } updateFormFieldsInPdfCalledByNgOnChanges(previousFormData) { if (!this.PDFViewerApplication?.pdfDocument?.annotationStorage) { // ngOnChanges calls this method too early - so just ignore it return; } for (const key in this.formData) { if (this.formData.hasOwnProperty(key)) { const newValue = this.formData[key]; if (newValue !== previousFormData[key]) { this.setFieldValueAndUpdateAnnotationStorage(key, newValue); } } } for (const key in previousFormData) { if (previousFormData.hasOwnProperty(key) && previousFormData[key]) { let hasPreviousValue = this.formData.hasOwnProperty(key); if (!hasPreviousValue) { const fullKey = Object.keys(this.formData).find((k) => k === key || k.endsWith('.' + key)); if (fullKey) { hasPreviousValue = this.formData.hasOwnProperty(fullKey); } } if (!hasPreviousValue) { this.setFieldValueAndUpdateAnnotationStorage(key, null); } } } } setFieldValueAndUpdateAnnotationStorage(key, newValue) { const radios = this.findRadioButtonGroup(key); if (radios) { radios.forEach((r) => { const activeValue = r.getAttribute('exportValue') ?? r.getAttribute('xfaon'); r.checked = activeValue === newValue; }); const updateFromAngular = new CustomEvent('updateFromAngular', { detail: newValue, }); radios[0].dispatchEvent(updateFromAngular); } else { const fieldId = this.findFormIdFromFieldName(key); if (fieldId) { const htmlField = this.formIdToField[fieldId]; if (htmlField) { if (htmlField instanceof HTMLInputElement && htmlField.type === 'checkbox') { let activeValue = htmlField.getAttribute('xfaon') ?? htmlField.getAttribute('exportvalue') ?? true; if (newValue === true || newValue === false) { activeValue = true; } htmlField.checked = activeValue === newValue; } else if (htmlField instanceof HTMLSelectElement) { this.populateSelectField(htmlField, newValue); } else { // textareas and input fields htmlField.value = newValue; } const updateFromAngular = new CustomEvent('updateFromAngular', { detail: newValue, }); htmlField.dispatchEvent(updateFromAngular); } else { console.error("Couldn't set the value of the field", key); } } } } populateSelectField(htmlField, newValue) { if (htmlField.multiple) { const { options } = htmlField; const newValueArray = newValue; for (let i = 0; i < options.length; i++) { const option = options.item(i); if (option) { option.selected = newValueArray.some((o) => o === option.value); } } } else { htmlField.value = newValue; } } findFormIdFromFieldName(fieldName) { if (Object.entries(this.formIdToFullFieldName).length === 0) { // sometimes, ngOnChanges() is called before initializing the PDF file return undefined; } const matchingEntries = Object.entries(this.formIdToFullFieldName).filter((entry) => entry[1] === fieldName || entry[1].endsWith('.' + fieldName)); if (matchingEntries.length > 1) { console.log(`More than one field name matches the field name ${fieldName}. Please use the one of these qualified field names:`, matchingEntries.map((f) => f[1])); console.log('ngx-extended-pdf-viewer uses the first matching field (which may or may not be the topmost field on your PDF form): ' + matchingEntries[0][0]); } else if (matchingEntries.length === 0) { console.log("Couldn't find the field " + fieldName); return undefined; } return matchingEntries[0][0]; } findRadioButtonGroup(fieldName) { const matchingEntries = Object.entries(this.radioButtons).filter((entry) => entry[0].endsWith('.' + fieldName) || entry[0] === fieldName); if (matchingEntries.length === 0) { return null; } if (matchingEntries.length > 1) { console.log('More than one radio button group name matches this name. Please use the qualified field name', matchingEntries.map((radio) => radio[0])); console.log('ngx-extended-pdf-viewer uses the first matching field (which may not be the topmost field on your PDF form): ' + matchingEntries[0][0]); } return matchingEntries[0][1]; } } var PdfSidebarView; (function (PdfSidebarView) { PdfSidebarView[PdfSidebarView["UNKNOWN"] = -1] = "UNKNOWN"; PdfSidebarView[PdfSidebarView["NONE"] = 0] = "NONE"; PdfSidebarView[PdfSidebarView["THUMBS"] = 1] = "THUMBS"; PdfSidebarView[PdfSidebarView["OUTLINE"] = 2] = "OUTLINE"; PdfSidebarView[PdfSidebarView["ATTACHMENTS"] = 3] = "ATTACHMENTS"; PdfSidebarView[PdfSidebarView["LAYERS"] = 4] = "LAYERS"; })(PdfSidebarView || (PdfSidebarView = {})); class PDFNotificationService { // this event is fired when the pdf.js library has been loaded and objects like PDFApplication are available onPDFJSInitSignal = signal(undefined); pdfjsVersion = getVersionSuffix(pdfDefaultOptions.assetsFolder); constructor() { effect(() => { if (this.onPDFJSInitSignal()) { this.pdfjsVersion = getVersionSuffix(pdfDefaultOptions.assetsFolder); } }); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PDFNotificationService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PDFNotificationService, providedIn: 'root' }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "17.3.12", ngImport: i0, type: PDFNotificationService, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [] }); class NgxExtendedPdfViewerService { rendererFactory; ngxExtendedPdfViewerInitialized = false; secondaryMenuIsEmpty = false; renderer; PDFViewerApplication; constructor(rendererFactory, notificationService) { this.rendererFactory = rendererFactory; this.renderer = this.rendererFactory.createRenderer(null, null); effect(() => { this.PDFViewerApplication = notificationService.onPDFJSInitSignal(); }); } find(text, options = {}) { if (!this.ngxExtendedPdfViewerInitialized) { // tslint:disable-next-line:quotemark console.error("The PDF viewer hasn't finished initializing. Please call find() later."); return undefined; } else { if (!options.useSecondaryFindcontroller) { const highlightAllCheckbox = document.getElementById('findHighlightAll'); if (highlightAllCheckbox) { highlightAllCheckbox.checked = options.highlightAll ?? false; } const matchCaseCheckbox = document.getElementById('findMatchCase'); if (matchCaseCheckbox) { matchCaseCheckbox.checked = options.matchCase ?? false; } const findMultipleCheckbox = document.getElementById('findMultiple'); if (findMultipleCheckbox) { findMultipleCheckbox.checked = options.findMultiple ?? false; } const entireWordCheckbox = document.getElementById('findEntireWord'); if (entireWordCheckbox) { entireWordCheckbox.checked = options.wholeWords ?? false; } const matchDiacriticsCheckbox = document.getElementById('findMatchDiacritics'); if (matchDiacriticsCheckbox) { matchDiacriticsCheckbox.checked = options.matchDiacritics ?? false; } const matchRegExpCheckbox = document.getElementById('matchRegExp'); if (matchRegExpCheckbox) { matchRegExpCheckbox.checked = options.regexp ?? false; if (matchRegExpCheckbox.checked) { if (findMultipleCheckbox) { findMultipleCheckbox.checked = false; } if (entireWordCheckbox) { entireWordCheckbox.checked = false; } if (matchDiacriticsCheckbox) { matchDiacriticsCheckbox.checked = false; } } if (findMultipleCheckbox) { findMultipleCheckbox.disabled = matchRegExpCheckbox.checked; } if (entireWordCheckbox) { entireWordCheckbox.disabled = matchRegExpCheckbox.checked; } if (matchDiacriticsCheckbox) { matchDiacriticsCheckbox.disabled = matchRegExpCheckbox.checked; } } const inputField = document.getElementById('findInput'); if (inputField && typeof text === 'string') { inputField.value = text; } } const findParameters = { caseSensitive: options.matchCase ?? false, entireWord: options.wholeWords ?? false, highlightAll: options.highlightAll ?? false, matchDiacritics: options.matchDiacritics ?? false, findMultiple: options.findMultiple, matchRegExp: options.regexp ?? false, findPrevious: false, query: text, source: null, type: 'find', dontScrollIntoView: options.dontScrollIntoView ?? false, }; const findController = options.useSecondaryFindcontroller ? this.PDFViewerApplication?.customFindController : this.PDFViewerApplication?.findController; const result = findController?.ngxFind(findParameters); return result; } } findNext(useSecondaryFindcontroller = false) { if (!this.ngxExtendedPdfViewerInitialized) { // tslint:disable-next-line:quotemark console.error("The PDF viewer hasn't finished initializing. Please call findNext() later."); return false; } else { const findController = useSecondaryFindcontroller ? this.PDFViewerApplication?.customFindController : this.PDFViewerApplication?.findController; findController?.ngxFindNext(); return true; } } findPrevious(useSecondaryFindcontroller = false) { if (!this.ngxExtendedPdfViewerInitialized) { // tslint:disable-next-line:quotemark console.error("The PDF viewer hasn't finished initializing. Please call findPrevious() later."); return false; } else { const findController = useSecondaryFindcontroller ? this.PDFViewerApplication?.customFindController : this.PDFViewerApplication?.findController; findController?.ngxFindPrevious(); return true; } } print(printRange) { if (this.PDFViewerApplication) { const alreadyPrinting = this.PDFViewerApplication?.PDFPrintServiceFactory?.isInPDFPrintRange !== undefined; if (!alreadyPrinting) { // slow down hurried users clicking the print button multiple times if (!printRange) { printRange = {}; } this.setPrintRange(printRange); this.PDFViewerApplication?.printPdf(); this.PDFViewerApplication?.eventBus.on('afterprint', this.removePrintRange.bind(this), { once: true }); } } } removePrintRange() { if (this.PDFViewerApplication?.PDFPrintServiceFactory) { delete this.PDFViewerApplication.PDFPrintServiceFactory.isInPDFPrintRange; delete this.PDFViewerApplication.PDFPrintServiceFactory.filteredPageCount; } } setPrintRange(printRange) { if (!this.PDFViewerApplication?.PDFPrintServiceFactory) { console.error("The print service hasn't been initialized yet."); return; } this.PDFViewerApplication.PDFPrintServiceFactory.isInPDFPrintRange = (page) => this.isInPDFPrintRange(page, printRange); this.PDFViewerApplication.PDFPrintServiceFactory.filteredPageCount = this.filteredPageCount(this.PDFViewerApplication?.pagesCount, printRange); } filteredPageCount(pageCount, range) { let result = 0; for (let page = 0; page < pageCount; page++) { if (this.isInPDFPrintRange(page, range)) { result++; } } return result; } isInPDFPrintRange(pageIndex, printRange) { const page = pageIndex + 1; if (printRange.from) { if (page < printRange.from) { return false; } } if (printRange.to) { if (page > printRange.to) { return false; } } if (printRange.excluded) { if (printRange.excluded.some((p) => p === page)) { return false; } } if (printRange.included) { if (!printRange.included.some((p) => p === page)) { return false; } } return true; } async getPageAsLines(pageNumber) { if (this.PDFViewerApplication) { const pdfDocument = this.PDFViewerApplication?.pdfDocument; const page = await pdfDocument.getPage(pageNumber); const textSnippets = (await page.getTextContent()).items // .filter((info) => !info['type']); // ignore the TextMarkedContent items const snippets = textSnippets; let minX = Number.MAX_SAFE_INTEGER; let minY = Number.MAX_SAFE_INTEGER; let maxX = Number.MIN_SAFE_INTEGER; let maxY = Number.MIN_SAFE_INTEGER; let countLTR = 0; let countRTL = 0; let text = ''; let lines = new Array(); for (let i = 0; i < snippets.length; i++) { const currentSnippet = snippets[i]; if (!currentSnippet.hasEOL) { const x = currentSnippet.transform[4]; const y = -currentSnippet.transform[5]; const width = currentSnippet.width; const height = currentSnippet.height; minX = Math.min(minX, x); minY = Math.min(minY, y); maxX = Math.max(maxX, x + width); maxY = Math.max(maxY, y + height); text += currentSnippet.str; if (currentSnippet.dir === 'rtl') { countRTL++; } if (currentSnippet.dir === 'ltr') { countLTR++; } } let addIt = i === snippets.length - 1 || currentSnippet.hasEOL; if (addIt) { let direction = undefined; if (countLTR > 0 && countRTL > 0) { direction = 'both'; } else if (countLTR > 0) { direction = 'ltr'; } else if (countRTL > 0) { direction = 'rtl'; } const line = { direction, x: minX, y: minY, width: maxX - minX, height: maxY - minY, text: text.trim(), }; lines.push(line); minX = Number.MAX_SAFE_INTEGER; minY = Number.MAX_SAFE_INTEGER; maxX = Number.MIN_SAFE_INTEGER; maxY = Number.MIN_SAFE_INTEGER; countLTR = 0; countRTL = 0; text = ''; } } return lines; } return []; } async getPageAsText(pageNumber) { if (!this.PDFViewerApplication) { return ''; } const pdfDocument = this.PDFViewerApplication?.pdfDocument; const page = await pdfDocument.getPage(pageNumber); const textSnippets = (await page.getTextContent()).items; return this.convertTextInfoToText(textSnippets); } convertTextInfoToText(textInfoItems) { if (!textInfoItems) { return ''; } return textInfoItems .filter((info) => !info['type']) .map((info) => (info.hasEOL ? info.str + '\n' : info.str)) .join(''); } async getPageAsCanvas(pageNumber, scale, background, backgroundColorToReplace = '#FFFFFF', annotationMode = AnnotationMode.ENABLE) { if (!this.PDFViewerApplication) { return Promise.resolve(undefined); } const pdfDocument = this.PDFViewerApplication.pdfDocument; const pdfPage = await pdfDocument.getPage(pageNumber); return this.draw(pdfPage, scale, background, backgroundColorToReplace, annotationMode); } async getPageAsImage(pageNumber, scale, background, backgroundColorToReplace = '#FFFFFF', annotationMode = AnnotationMode.ENABLE) { const canvas = await this.getPageAsCanvas(pageNumber, scale, background, backgroundColorToReplace, annotationMode); return canvas?.toDataURL(); } async draw(pdfPage, scale, background, backgroundColorToReplace = '#FFFFFF', annotationMode = AnnotationMode.ENABLE) { let zoomFactor = 1; if (scale.scale) { zoomFactor = scale.scale; } else if (scale.width) { zoomFactor = scale.width / pdfPage.getViewport({ scale: 1 }).width; } else if (scale.height) { zoomFactor = scale.height / pdfPage.getViewport({ scale: 1 }).height; } const viewport = pdfPage.getViewport({ scale: zoomFactor, }); const { ctx, canvas } = this.getPageDrawContext(viewport.width, viewport.height); const drawViewport = viewport.clone(); const renderContext = { canvasContext: ctx, viewport: drawViewport, background, backgroundColorToReplace, annotationMode, }; const renderTask = pdfPage.render(renderContext); const dataUrlPromise = () => Promise.resolve(canvas); return renderTask.promise.then(dataUrlPromise); } getPageDrawContext(width, height) { const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d', { alpha: true }); if (!ctx) { // tslint:disable-next-line: quotemark throw new Error("Couldn't create the 2d context"); } canvas.width = width; canvas.height = height; this.renderer.setStyle(canvas, 'width', `${width}px`); this.renderer.setStyle(canvas, 'height', `${height}px`); return { ctx, canvas }; } async getCurrentDocumentAsBlob() { return (await this.PDFViewerApplication?.export()) || undefined; } async getFormData(currentFormValues = true) { if (!this.PDFViewerApplication) { return []; } const pdf = this.PDFViewerApplication?.pdfDocument; // screen DPI / PDF DPI const dpiRatio = 96 / 72; const result = []; for (let i = 1; i <= pdf?.numPages; i++) { // track the current page const currentPage /* : PDFPageProxy */ = await pdf.getPage(i); const annotations = await currentPage.getAnnotations(); annotations .filter((a) => a.subtype === 'Widget') // get the form field annotations only .map((a) => ({ ...a })) // only expose copies of the annotations to avoid side-effects .forEach((a) => { // get the rectangle that represent the single field // and resize it according to the current DPI const fieldRect = currentPage.getViewport({ scale: dpiRatio }).convertToViewportRectangle(a.rect); // add the corresponding input if (currentFormValues && a.fieldName) { try { if (a.exportValue) { const currentValue = this.PDFViewerApplication?.pdfDocument.annotationStorage.getValue(a.id, a.fieldName + '/' + a.exportValue, ''); a.value = currentValue?.value; } else if (a.radioButton) { const currentValue = this.PDFViewerApplication?.pdfDocument.annotationStorage.getValue(a.id, a.fieldName + '/' + a.fieldValue, ''); a.value = currentValue?.value; } else { const currentValue = this.PDFViewerApplication?.pdfDocument.annotationStorage.getValue(a.id, a.fieldName, ''); a.value = currentValue?.value; } } catch (exception) { // just ignore it } } result.push({ fieldAnnotation: a, fieldRect, pageNumber: i }); }); } return result; } /** * Adds a page to the rendering queue * @param {number} pageIndex Index of the page to render * @returns {boolean} false, if the page has already been rendered, * if it's out of range or if the viewer hasn't been initialized yet */ addPageToRenderQueue(pageIndex) { return this.PDFViewerApplication?.pdfViewer.addPageToRenderQueue(pageIndex) ?? false; } isRenderQueueEmpty() { const scrolledDown = true; const renderExtra = false; if (this.PDFViewerApplication) { const nextPage = this.PDFViewerApplication.pdfViewer.renderingQueue.getHighestPriority(this.PDFViewerApplication?.pdfViewer._getVisiblePages(), this.PDFViewerApplication?.pdfViewer._pages, scrolledDown, renderExtra); return !nextPage; } return true; } hasPageBeenRendered(pageIndex) { if (!this.PDFViewerApplication) { return false; } const pages = this.PDFViewerApplication?.pdfViewer._pages; if (pages.length > pageIndex && pageIndex >= 0) { const pageView = pages[pageIndex]; const hasBeenRendered = pageView.renderingState === 3; return hasBeenRendered; } return false; } sleep(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async renderPage(pageIndex) { if (!this.hasPageBeenRendered(pageIndex)) { await this.addPageToRenderQueue(pageIndex); while (!this.hasPageBeenRendered(pageIndex)) { await this.sleep(7); } } } currentlyRenderedPages() { if (!this.PDFViewerApplication) { return []; } const pages = this.PDFViewerApplication?.pdfViewer._pages; return pages.filter((page) => page.renderingState === 3).map((page) => page.id); } numberOfPages() { if (!this.PDFViewerApplication) { return 0; } const pages = this.PDFViewerApplication?.pdfViewer._pages; return pages.length; } getCurrentlyVisiblePageNumbers() { const app = this.PDFViewerApplication; if (!app) { return []; } const pages = app.pdfViewer._getVisiblePages().views; return pages?.map((page) => page.id); } async listLayers() { if (!this.PDFViewerApplication) { return []; } const optionalContentConfig = await this.PDFViewerApplication?.pdfViewer.optionalContentConfigPromise; if (optionalContentConfig) { const levelData = optionalContentConfig.getOrder(); const layerIds = levelData.filter((groupId) => typeof groupId !== 'object'); return layerIds.map((layerId) => { const config = optionalContentConfig.getGroup(layerId); return { layerId: layerId, name: config.name, visible: config.visible, }; }); } return undefined; } async toggleLayer(layerId) {