diff --git a/EnvelopeGenerator.Web/appsettings.UI.json b/EnvelopeGenerator.Web/appsettings.UI.json index 4be53a04..03298588 100644 --- a/EnvelopeGenerator.Web/appsettings.UI.json +++ b/EnvelopeGenerator.Web/appsettings.UI.json @@ -23,19 +23,20 @@ "Annotations": [ { "Name": "Signature", - "MarginTop": 0 + "MarginTop": 0, + "MarginLeft": 0.20 }, { "Name": "PositionLabel", "VerBoundAnnotName": "Signature", "WidthRatio": 1.2, "HeightRatio": 0.5, - "MarginTopRatio": 0.22 + "MarginTopRatio": 0.30 }, { "Name": "Position", "VerBoundAnnotName": "PositionLabel", - "WidthRatio": 1.2, + "WidthRatio": 1.4, "HeightRatio": 0.5, "MarginTopRatio": -0.05 }, @@ -49,7 +50,7 @@ { "Name": "City", "VerBoundAnnotName": "CityLabel", - "WidthRatio": 1.2, + "WidthRatio": 1.4, "HeightRatio": 0.5, "MarginTopRatio": -0.05 }, diff --git a/EnvelopeGenerator.Web/wwwroot/js/annotation.js b/EnvelopeGenerator.Web/wwwroot/js/annotation.js index debb5e88..b893a182 100644 --- a/EnvelopeGenerator.Web/wwwroot/js/annotation.js +++ b/EnvelopeGenerator.Web/wwwroot/js/annotation.js @@ -11,13 +11,16 @@ async function createAnnotations(document, envelopeId, receiverId) { //#region signatures const id = generateId(envelopeId, receiverId, element.id, 'signature'); + + const boundingBox = new PSPDFKit.Geometry.Rect(annotParams.signature); + const annotation = new PSPDFKit.Annotations.WidgetAnnotation({ id: id, pageIndex: page, formFieldName: id, - backgroundColor: PSPDFKit.Color.LIGHT_YELLOW, + backgroundColor: null, blendMode: 'normal', - boundingBox: new PSPDFKit.Geometry.Rect(annotParams.signature), + boundingBox: boundingBox, }) const formField = new PSPDFKit.FormFields.SignatureFormField({ @@ -237,7 +240,7 @@ async function createAnnotationFrameBlob(receiverName, receiverSignature, timest Comp.SignatureProgress.SignedCount += 1; const canvas = document.createElement('canvas') const scale = 4 - const fontSize = 10 + const fontSize = 8 canvas.width = width * scale canvas.height = height * scale @@ -270,21 +273,17 @@ async function createAnnotationFrameBlob(receiverName, receiverSignature, timest ctx.moveTo(30 * scale, 50 * scale) ctx.lineTo(signatureLength, 50 * scale) - ctx.strokeStyle = 'darkblue' + ctx.strokeStyle = 'darkgray' ctx.stroke() ctx.fillStyle = 'black' ctx.font = `${fontSize * scale}px sans-serif` ctx.fillText((READ_AND_CONFIRM ? localized.confirmedBy : localized.signedBy) ?? 'Signed by', 15 * scale, 10 * scale) - ctx.fillText(receiverName, 15 * scale, 60 * scale) - ctx.fillText(signatureString, 15 * scale, 70 * scale) + ctx.fillText(receiverName, 15 * scale, 58.5 * scale) + ctx.fillText(signatureString, 15 * scale, 69 * scale) - return new Promise((resolve) => { - canvas.toBlob((blob) => { - const url = URL.createObjectURL(blob) - resolve(url) - }) - }) + const blob = await new Promise((resolve) => canvas.toBlob(resolve)) + return URL.createObjectURL(blob) } //required diff --git a/EnvelopeGenerator.Web/wwwroot/js/app.js b/EnvelopeGenerator.Web/wwwroot/js/app.js index 62a329e9..0f035ee0 100644 --- a/EnvelopeGenerator.Web/wwwroot/js/app.js +++ b/EnvelopeGenerator.Web/wwwroot/js/app.js @@ -37,10 +37,15 @@ class App { }); // 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); } @@ -66,8 +71,27 @@ class App { if (isFormField === false && isSignature === true) { - const left = annotation.boundingBox.left - 20 - const top = annotation.boundingBox.top - 20 + // 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() diff --git a/EnvelopeGenerator.Web/wwwroot/js/app.min.js b/EnvelopeGenerator.Web/wwwroot/js/app.min.js index 8d9a209a..a777361d 100644 --- a/EnvelopeGenerator.Web/wwwroot/js/app.min.js +++ b/EnvelopeGenerator.Web/wwwroot/js/app.min.js @@ -1,3 +1,3 @@ -const formatLocalized=(n,...t)=>typeof n=="string"?n.replace(/\{(\d+)\}/g,(n,i)=>t[i]??""):"";class App{constructor(n,t,i,r,u,f){this.container=f??`#${this.constructor.name.toLowerCase()}`;this.envelopeKey=n;this.pdfKit=null;this.currentDocument=t.envelope.documents[0];this.currentReceiver=t.receiver;this.signatureCount=t.envelope.documents[0].elements.length;this.envelopeReceiver=t;this.documentBytes=i;this.licenseKey=r;this.locale=u}async init(){if(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()}),!READ_AND_CONFIRM)try{let n=await createAnnotations(this.currentDocument,this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId);await this.pdfKit.create(n)}catch(n){console.error("Error loading annotations:",n)}READ_AND_CONFIRM||[...document.getElementsByClassName("btn_refresh")].forEach(n=>n.addEventListener("click",()=>this.handleClick("RESET")));[...document.getElementsByClassName("btn_complete")].forEach(n=>n.addEventListener("click",()=>this.handleClick("FINISH")));[...document.getElementsByClassName("btn_reject")].forEach(n=>n.addEventListener("click",()=>this.handleClick("REJECT")))}handleAnnotationsLoad(n){n.toJS()}handleAnnotationsChange(){}async handleAnnotationsCreate(n){const t=n.toJS()[0],i=!!t.formFieldName,r=!!t.isSignature;if(i===!1&&r===!0){const r=t.boundingBox.left-20,u=t.boundingBox.top-20,n=150,i=75,f=new Date,e=await createAnnotationFrameBlob(this.envelopeReceiver.name,this.currentReceiver.signature,f,n,i),o=await fetch(e),s=await o.blob(),h=await this.pdfKit.createAttachment(s),c=createImageAnnotation(new PSPDFKit.Geometry.Rect({left:r,top:u,width:n,height:i}),t.pageIndex,h,generateId(this.envelopeReceiver.envelopeId,this.envelopeReceiver.receiverId,this.fakeElementId--,"signed"));this.pdfKit.create(c)}}async handleClick(n){let t=!1;switch(n){case"RESET":t=await this.handleReset(null);Comp.SignatureProgress.SignedCount=0;t.isConfirmed&&Swal.fire({title:localized.success,text:localized.documentReset,icon:"info"});break;case"FINISH":t=await this.handleFinish(null);t==!0&&(window.location.href=`/Envelope/${this.envelopeKey}`);break;case"REJECT":Swal.fire({title:localized.rejection,html:`