const formatLocalized = (template, ...args) => typeof template === 'string' ? template.replace(/\{(\d+)\}/g, (_, idx) => args[idx] ?? '') : ''; class App { constructor(envelopeKey, envelopeReceiver, documentBytes, licenseKey, locale, container) { this.container = container ?? `#${this.constructor.name.toLowerCase()}`; this.envelopeKey = envelopeKey this.pdfKit = null this.currentDocument = envelopeReceiver.envelope.documents[0] this.currentReceiver = envelopeReceiver.receiver this.signatureCount = envelopeReceiver.envelope.documents[0].elements.length; this.envelopeReceiver = envelopeReceiver; this.documentBytes = documentBytes; this.licenseKey = licenseKey; this.locale = locale; } async init() { // Load PSPDFKit this.pdfKit = await loadPSPDFKit(this.documentBytes, this.container, this.licenseKey, this.locale) addToolbarItems(this.pdfKit, this.handleClick.bind(this)) this.pdfKit.addEventListener( 'annotations.load', this.handleAnnotationsLoad.bind(this) ) this.pdfKit.addEventListener( 'annotations.change', this.handleAnnotationsChange.bind(this) ) this.pdfKit.addEventListener( 'annotations.create', this.handleAnnotationsCreate.bind(this) ) this.pdfKit.addEventListener("annotations.willChange", _ => { Comp.ActPanel.Toggle(); }); // Load annotations into PSPDFKit this.signatureWidgets = [] // { pageIndex, boundingBox, id } if (!READ_AND_CONFIRM) try { let signatures = await createAnnotations(this.currentDocument, this.envelopeReceiver.envelopeId, this.envelopeReceiver.receiverId); await this.pdfKit.create(signatures); // Cache signature widget bounding boxes for stable frame placement this.signatureWidgets = signatures .filter(s => s instanceof PSPDFKit.Annotations.WidgetAnnotation && s.formFieldName?.includes('signature')) .map(s => ({ id: s.id, pageIndex: s.pageIndex, boundingBox: s.boundingBox.toJS() })) } catch (e) { console.error("Error loading annotations:", e); } //add click events of external buttons if (!READ_AND_CONFIRM) { [...document.getElementsByClassName('btn_refresh')].forEach(btn => btn.addEventListener('click', _ => this.handleClick('RESET'))); } [...document.getElementsByClassName('btn_complete')].forEach(btn => btn.addEventListener('click', _ => this.handleClick('FINISH'))); [...document.getElementsByClassName('btn_reject')].forEach(btn => btn.addEventListener('click', _ => this.handleClick('REJECT'))); } handleAnnotationsLoad(loadedAnnotations) { loadedAnnotations.toJS() } handleAnnotationsChange() { } async handleAnnotationsCreate(createdAnnotations) { const annotation = createdAnnotations.toJS()[0] const isFormField = !!annotation.formFieldName const isSignature = !!annotation.isSignature if (isFormField === false && isSignature === true) { // Find the nearest signature widget on the same page to use its stable bounding box const inkBBox = annotation.boundingBox const samePageWidgets = this.signatureWidgets.filter(w => w.pageIndex === annotation.pageIndex) const nearestWidget = samePageWidgets.reduce((nearest, w) => { const dx = w.boundingBox.left - inkBBox.left const dy = w.boundingBox.top - inkBBox.top const dist = Math.sqrt(dx * dx + dy * dy) return dist < nearest.dist ? { dist, widget: w } : nearest }, { dist: Infinity, widget: null }).widget // 1. Remove widget background after signing if (nearestWidget) { const pageAnnots = await this.pdfKit.getAnnotations(annotation.pageIndex) const widgetAnnot = pageAnnots.find(a => a.id === nearestWidget.id) if (widgetAnnot) await this.pdfKit.update(widgetAnnot.set('backgroundColor', PSPDFKit.Color.TRANSPARENT)) } const bbox = nearestWidget?.boundingBox ?? inkBBox const left = bbox.left - 20 const top = bbox.top - 12 const width = 150 const height = 75 const timestamp = new Date() const imageUrl = await createAnnotationFrameBlob( this.envelopeReceiver.name, this.currentReceiver.signature, timestamp, width, height ) const request = await fetch(imageUrl) const blob = await request.blob() const imageAttachmentId = await this.pdfKit.createAttachment(blob) const frameAnnotation = createImageAnnotation( new PSPDFKit.Geometry.Rect({ left: left, top: top, width: width, height: height, }), annotation.pageIndex, imageAttachmentId, generateId( this.envelopeReceiver.envelopeId, this.envelopeReceiver.receiverId, this.fakeElementId--, 'signed' ) ) this.pdfKit.create(frameAnnotation) } } async handleClick(eventType) { let result = false switch (eventType) { case 'RESET': result = await this.handleReset(null) Comp.SignatureProgress.SignedCount = 0; if (result.isConfirmed) { Swal.fire({ title: localized.success, text: localized.documentReset, icon: 'info', }) } break; case 'FINISH': result = await this.handleFinish(null) if (result == true) { // Redirect to success page after saving to database window.location.href = `/Envelope/${this.envelopeKey}` } break; case 'REJECT': Swal.fire({ title: localized.rejection, html: `