sharper signature frame and text

This commit is contained in:
Jonathan Jenne 2023-12-11 10:11:32 +01:00
parent fd4dd8b457
commit d8ba020bb9
2 changed files with 356 additions and 333 deletions

View File

@ -1,156 +1,173 @@
class Annotation { class Annotation {
createAnnotations(document) { createAnnotations(document) {
const annotations = [] const annotations = []
document.elements.forEach((element) => { document.elements.forEach((element) => {
console.log('Creating annotation for element', element.id) console.log('Creating annotation for element', element.id)
const [annotation, formField] = this.createAnnotationFromElement(element) const [annotation, formField] = this.createAnnotationFromElement(element)
annotations.push(annotation) annotations.push(annotation)
annotations.push(formField) annotations.push(formField)
}) })
return annotations return annotations
} }
async deleteAnnotations(instance) { async deleteAnnotations(instance) {
let pageAnnotations = ( let pageAnnotations = (
await Promise.all( await Promise.all(
Array.from({ length: instance.totalPageCount }).map((_, pageIndex) => Array.from({ length: instance.totalPageCount }).map((_, pageIndex) =>
instance.getAnnotations(pageIndex) instance.getAnnotations(pageIndex)
)
)
) )
) .flatMap((annotations) =>
) annotations.reduce((acc, annotation) => acc.concat(annotation), [])
.flatMap((annotations) => )
annotations.reduce((acc, annotation) => acc.concat(annotation), []) .filter(
) (annotation) =>
.filter( !!annotation.isSignature || annotation.description == 'FRAME'
(annotation) => )
!!annotation.isSignature || annotation.description == 'FRAME' //deleting all Annotations
) return await instance.delete(pageAnnotations)
//deleting all Annotations }
return await instance.delete(pageAnnotations)
}
async validateAnnotations(instance) { async validateAnnotations(instance) {
let pageAnnotations = ( let pageAnnotations = (
await Promise.all( await Promise.all(
Array.from({ length: instance.totalPageCount }).map((_, pageIndex) => Array.from({ length: instance.totalPageCount }).map((_, pageIndex) =>
instance.getAnnotations(pageIndex) instance.getAnnotations(pageIndex)
)
)
) )
) .flatMap((annotations) =>
) annotations.reduce((acc, annotation) => acc.concat(annotation), [])
.flatMap((annotations) => )
annotations.reduce((acc, annotation) => acc.concat(annotation), []) .map((annotation) => {
) console.log(annotation.toJS())
.map((annotation) => { return annotation
console.log(annotation.toJS()) })
return true
}
createAnnotationFromElement(element) {
const id = PSPDFKit.generateInstantId()
const width = this.inchToPoint(element.width)
const height = this.inchToPoint(element.height)
const top = this.inchToPoint(element.top) - height / 2
const left = this.inchToPoint(element.left) - width / 2
const page = element.page - 1
const annotation = this.createSignatureAnnotation(
id,
width,
height,
top,
left,
page
)
const formField = new PSPDFKit.FormFields.SignatureFormField({
name: id,
annotationIds: PSPDFKit.Immutable.List([annotation.id]),
})
return [annotation, formField]
}
createSignatureAnnotation(id, width, height, top, left, pageIndex) {
const annotation = new PSPDFKit.Annotations.WidgetAnnotation({
id: id,
pageIndex: pageIndex,
formFieldName: id,
backgroundColor: PSPDFKit.Color.YELLOW,
blendMode: 'multiply',
boundingBox: new PSPDFKit.Geometry.Rect({
width,
height,
top,
left,
}),
})
return annotation return annotation
}) }
return true createImageAnnotation(boundingBox, pageIndex, imageAttachmentId) {
} const frameAnnotation = new PSPDFKit.Annotations.ImageAnnotation({
pageIndex: pageIndex,
isSignature: false,
readOnly: true,
locked: true,
lockedContents: true,
contentType: 'image/png',
imageAttachmentId,
description: 'FRAME',
boundingBox: boundingBox,
})
return frameAnnotation
}
createAnnotationFromElement(element) { async createAnnotationFrameBlob(receiverName, receiverSignature, width, height) {
const id = PSPDFKit.generateInstantId() const canvas = document.createElement('canvas')
const width = this.inchToPoint(element.width) const scale = 4
const height = this.inchToPoint(element.height) const fontSize = 10
const top = this.inchToPoint(element.top) - height / 2
const left = this.inchToPoint(element.left) - width / 2
const page = element.page - 1
const annotation = this.createSignatureAnnotation(
id,
width,
height,
top,
left,
page
)
const formField = new PSPDFKit.FormFields.SignatureFormField({ console.log(receiverSignature)
name: id,
annotationIds: PSPDFKit.Immutable.List([annotation.id]),
})
return [annotation, formField] //canvas.width = width
} //canvas.height = height
createSignatureAnnotation(id, width, height, top, left, pageIndex) { canvas.width = width * scale
const annotation = new PSPDFKit.Annotations.WidgetAnnotation({ canvas.height = height * scale
id: id,
pageIndex: pageIndex,
formFieldName: id,
backgroundColor: PSPDFKit.Color.YELLOW,
blendMode: 'multiply',
boundingBox: new PSPDFKit.Geometry.Rect({
width,
height,
top,
left,
}),
})
return annotation //canvas.style.width = width * 4
} //canvas.style.height = height * 4
createImageAnnotation(boundingBox, pageIndex, imageAttachmentId) { const ctx = canvas.getContext('2d')
const frameAnnotation = new PSPDFKit.Annotations.ImageAnnotation({ // This supposedly makes the lines and text less blurry
pageIndex: pageIndex, // See: https://stackoverflow.com/questions/8696631/canvas-drawings-like-lines-are-blurry
isSignature: false, ctx.translate(0.5, 0.5)
readOnly: true,
locked: true,
lockedContents: true,
contentType: 'image/png',
imageAttachmentId,
description: 'FRAME',
boundingBox: boundingBox,
})
return frameAnnotation
}
async createAnnotationFrameBlob(receiverName, width, height) { // This also should make the lines and text less blurry
const canvas = document.createElement('canvas') ctx.textRendering = "geometricPrecision"
canvas.width = width
canvas.height = height
const ctx = canvas.getContext('2d') const date = new Date()
const dateString = date.toLocaleDateString('de-DE')
const date = new Date() const signatureLength = 100 * scale
const dateString = date.toLocaleDateString('de-DE')
const signatureLength = 100 ctx.beginPath()
ctx.beginPath() ctx.moveTo(30 * scale, 10 * scale)
ctx.lineTo(signatureLength, 10 * scale)
ctx.moveTo(30, 10) ctx.moveTo(30 * scale, 10 * scale)
ctx.lineTo(signatureLength, 10) ctx.arcTo(10 * scale, 10 * scale, 10 * scale, 30 * scale, 20 * scale)
ctx.moveTo(30, 10) ctx.moveTo(10 * scale, 30 * scale)
ctx.arcTo(10, 10, 10, 30, 20) ctx.arcTo(10 * scale, 50 * scale, 30 * scale, 50 * scale, 20 * scale)
ctx.moveTo(10, 30) ctx.moveTo(30 * scale, 50 * scale)
ctx.arcTo(10, 50, 30, 50, 20) ctx.lineTo(signatureLength, 50 * scale)
ctx.moveTo(30, 50) ctx.strokeStyle = 'darkblue'
ctx.lineTo(signatureLength, 50) ctx.stroke()
ctx.strokeStyle = 'darkblue' ctx.fillStyle = 'black'
ctx.stroke() ctx.font = `${fontSize * scale}px sans-serif`
ctx.fillText('Signed by', 30 * scale, 10 * scale)
ctx.fillText(receiverName + ', ' + dateString, 15 * scale, 60 * scale)
ctx.fillStyle = 'black' return new Promise((resolve) => {
ctx.font = '10px serif' canvas.toBlob((blob) => {
ctx.fillText('Signed by', 30, 10) const url = URL.createObjectURL(blob)
ctx.fillText(receiverName + ', ' + dateString, 15, 60) resolve(url)
})
})
}
return new Promise((resolve) => { inchToPoint(inch) {
canvas.toBlob((blob) => { return inch * 72
const url = URL.createObjectURL(blob) }
resolve(url)
})
})
}
inchToPoint(inch) {
return inch * 72
}
} }

View File

@ -1,230 +1,236 @@
const ActionType = { const ActionType = {
Created: 0, Created: 0,
Saved: 1, Saved: 1,
Sent: 2, Sent: 2,
EmailSent: 3, EmailSent: 3,
Delivered: 4, Delivered: 4,
Seen: 5, Seen: 5,
Signed: 6, Signed: 6,
Rejected: 7, Rejected: 7,
} }
class App { class App {
constructor(container, envelopeKey) { constructor(container, envelopeKey) {
this.container = container this.container = container
this.envelopeKey = envelopeKey this.envelopeKey = envelopeKey
// Initialize classes // Initialize classes
console.debug('Initializing classes..') console.debug('Initializing classes..')
this.UI = new UI() this.UI = new UI()
this.Network = new Network() this.Network = new Network()
this.Annotation = new Annotation() this.Annotation = new Annotation()
this.Instance = null this.Instance = null
this.currentDocument = null this.currentDocument = null
this.currentReceiver = null this.currentReceiver = null
}
// This function will be called in the ShowEnvelope.razor page
// and will trigger loading of the Editor Interface
async init() {
// Load the envelope from the database
console.debug('Loading envelope from database..')
const envelopeResponse = await this.Network.getEnvelope(this.envelopeKey)
const envelopeError = !!envelopeResponse.error
if (envelopeError) {
return Swal.fire({
title: 'Fehler',
text: 'Umschlag konnte nicht geladen werden!',
icon: 'error',
})
} }
this.currentDocument = envelopeResponse.data.envelope.documents[0] // This function will be called in the ShowEnvelope.razor page
this.currentReceiver = envelopeResponse.data.receiver // and will trigger loading of the Editor Interface
async init() {
// Load the envelope from the database
console.debug('Loading envelope from database..')
const envelopeResponse = await this.Network.getEnvelope(this.envelopeKey)
const envelopeError = !!envelopeResponse.error
// Load the document from the filestore if (envelopeError) {
console.debug('Loading document from filestore') return Swal.fire({
const documentResponse = await this.Network.getDocument( title: 'Fehler',
this.envelopeKey, text: 'Umschlag konnte nicht geladen werden!',
this.currentDocument.id icon: 'error',
) })
const documentError = !!documentResponse.error
if (documentError) {
console.error(documentResponse.error)
return Swal.fire({
title: 'Fehler',
text: 'Dokument konnte nicht geladen werden!',
icon: 'error',
})
}
const arrayBuffer = documentResponse.data
// Load PSPDFKit
console.debug('Loading PSPDFKit..')
this.Instance = await this.UI.loadPSPDFKit(arrayBuffer, this.container)
this.UI.configurePSPDFKit(this.Instance, this.handleClick.bind(this))
this.Instance.addEventListener(
'annotations.load',
this.handleAnnotationsLoad.bind(this)
)
this.Instance.addEventListener(
'annotations.change',
this.handleAnnotationsChange.bind(this)
)
this.Instance.addEventListener(
'annotations.create',
this.handleAnnotationsCreate.bind(this)
)
// Load annotations into PSPDFKit
console.debug('Loading annotations..')
try {
const annotations = this.Annotation.createAnnotations(
this.currentDocument
)
const createdAnnotations = await this.Instance.create(annotations)
await this.Network.openDocument(this.envelopeKey)
} catch (e) {
console.error(e)
}
}
handleAnnotationsLoad(loadedAnnotations) {
console.log('annotations loaded', loadedAnnotations.toJS())
}
handleAnnotationsChange() {}
async handleAnnotationsCreate(createdAnnotations) {
const annotation = createdAnnotations.toJS()[0]
const isFormField = !!annotation.formFieldName
const isSignature = !!annotation.isSignature
if (isFormField === false && isSignature === true) {
const left = annotation.boundingBox.left - 25
const top = annotation.boundingBox.top - 25
const width = 150
const height = 75
const imageUrl = await this.Annotation.createAnnotationFrameBlob(
this.currentReceiver.name,
width,
height
)
const request = await fetch(imageUrl)
const blob = await request.blob()
const imageAttachmentId = await this.Instance.createAttachment(blob)
const frameAnnotation = this.Annotation.createImageAnnotation(
new PSPDFKit.Geometry.Rect({
left: left,
top: top,
width: width,
height: height,
}),
annotation.pageIndex,
imageAttachmentId
)
this.Instance.create(frameAnnotation)
}
}
async handleClick(eventType) {
let result = false
switch (eventType) {
case 'RESET':
result = await this.handleReset(null)
if (result == true) {
Swal.fire({
title: 'Erfolg',
text: 'Dokument wurde zurückgesetzt',
icon: 'info',
})
} else {
Swal.fire({
title: 'Fehler',
text: 'Dokument konnte nicht zurückgesetzt werden!',
icon: 'error',
})
} }
break this.currentDocument = envelopeResponse.data.envelope.documents[0]
this.currentReceiver = envelopeResponse.data.receiver
case 'FINISH': // Load the document from the filestore
result = await this.handleFinish(null) console.debug('Loading document from filestore')
const documentResponse = await this.Network.getDocument(
this.envelopeKey,
this.currentDocument.id
)
const documentError = !!documentResponse.error
if (result == true) { if (documentError) {
// Redirect to success page after saving to database console.error(documentResponse.error)
window.location.href = `/EnvelopeKey/${this.envelopeKey}/Success` return Swal.fire({
} else { title: 'Fehler',
alert('Fehler beim Abschließen des Dokuments!') text: 'Dokument konnte nicht geladen werden!',
icon: 'error',
})
} }
break const arrayBuffer = documentResponse.data
case 'REJECT': // Load PSPDFKit
alert('Dokument abgelent!') console.debug('Loading PSPDFKit..')
} this.Instance = await this.UI.loadPSPDFKit(arrayBuffer, this.container)
} this.UI.configurePSPDFKit(this.Instance, this.handleClick.bind(this))
async handleFinish(event) { this.Instance.addEventListener(
// Save changes before doing anything 'annotations.load',
try { this.handleAnnotationsLoad.bind(this)
await this.Instance.save() )
} catch (e) { this.Instance.addEventListener(
console.error(e) 'annotations.change',
return false this.handleAnnotationsChange.bind(this)
)
this.Instance.addEventListener(
'annotations.create',
this.handleAnnotationsCreate.bind(this)
)
// Load annotations into PSPDFKit
console.debug('Loading annotations..')
try {
const annotations = this.Annotation.createAnnotations(
this.currentDocument
)
const createdAnnotations = await this.Instance.create(annotations)
await this.Network.openDocument(this.envelopeKey)
} catch (e) {
console.error(e)
}
} }
// Export annotation data and save to database handleAnnotationsLoad(loadedAnnotations) {
try { console.log('annotations loaded', loadedAnnotations.toJS())
const json = await this.Instance.exportInstantJSON() }
const postEnvelopeResult = await this.Network.postEnvelope(
this.envelopeKey,
this.currentDocument.id,
JSON.stringify(json)
)
console.log(postEnvelopeResult) handleAnnotationsChange() { }
async handleAnnotationsCreate(createdAnnotations) {
const annotation = createdAnnotations.toJS()[0]
const isFormField = !!annotation.formFieldName
const isSignature = !!annotation.isSignature
if (isFormField === false && isSignature === true) {
const left = annotation.boundingBox.left - 25
const top = annotation.boundingBox.top - 25
const width = 150
const height = 75
console.log(createdAnnotations)
console.log(annotation)
const imageUrl = await this.Annotation.createAnnotationFrameBlob(
this.currentReceiver.name,
this.currentReceiver.signature,
width,
height
)
const request = await fetch(imageUrl)
const blob = await request.blob()
const imageAttachmentId = await this.Instance.createAttachment(blob)
const frameAnnotation = this.Annotation.createImageAnnotation(
new PSPDFKit.Geometry.Rect({
left: left,
top: top,
width: width,
height: height,
}),
annotation.pageIndex,
imageAttachmentId
)
this.Instance.create(frameAnnotation)
}
}
async handleClick(eventType) {
let result = false
switch (eventType) {
case 'RESET':
result = await this.handleReset(null)
if (result == true) {
Swal.fire({
title: 'Erfolg',
text: 'Dokument wurde zurückgesetzt',
icon: 'info',
})
} else {
Swal.fire({
title: 'Fehler',
text: 'Dokument konnte nicht zurückgesetzt werden!',
icon: 'error',
})
}
break
case 'FINISH':
result = await this.handleFinish(null)
if (result == true) {
// Redirect to success page after saving to database
window.location.href = `/EnvelopeKey/${this.envelopeKey}/Success`
} else {
alert('Fehler beim Abschließen des Dokuments!')
}
break
case 'REJECT':
alert('Dokument abgelent!')
}
}
async handleFinish(event) {
// Save changes before doing anything
try {
await this.Instance.save()
} catch (e) {
console.error(e)
return false
}
// Export annotation data and save to database
try {
const json = await this.Instance.exportInstantJSON()
const postEnvelopeResult = await this.Network.postEnvelope(
this.envelopeKey,
this.currentDocument.id,
JSON.stringify(json)
)
console.log(postEnvelopeResult)
if (postEnvelopeResult === false) {
return false
}
} catch (e) {
console.error(e)
return false
}
return true
}
async handleReset(event) {
const result = await Swal.fire({
title: 'Sind sie sicher?',
text: 'Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?',
icon: 'question',
})
if (result.isConfirmed) {
const result = this.Annotation.deleteAnnotations(this.Instance)
return true
}
if (result.isDimissed) {
return true
}
if (postEnvelopeResult === false) {
return false return false
}
} catch (e) {
console.error(e)
return false
} }
return true
}
async handleReset(event) {
const result = await Swal.fire({
title: 'Sind sie sicher?',
text: 'Wollen Sie das Dokument und alle erstellten Signaturen zurücksetzen?',
icon: 'question',
})
if (result.isConfirmed) {
const result = this.Annotation.deleteAnnotations(this.Instance)
return true
}
if (result.isDimissed) {
return true
}
return false
}
} }