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
JavaScript
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) {