Previously, signature annotations did not include elementId mapping logic, which caused issues when linking annotations to their corresponding elements. This update adds logic to extract elementId from the nearest signature annotation (similar to frame annotations) to ensure proper association.
353 lines
12 KiB
JavaScript
353 lines
12 KiB
JavaScript
function generateId(envelopeId, receiverId, elementId, annotationType) {
|
|
return `${envelopeId}#${receiverId}#${elementId}#${annotationType}`;
|
|
}
|
|
|
|
async function createAnnotations(document, envelopeId, receiverId) {
|
|
const signatures = [];
|
|
|
|
for (let element of document.elements) {
|
|
const annotParams = await getAnnotationParams(element.left, element.top);
|
|
const page = element.page - 1
|
|
|
|
//#region signatures
|
|
const id = generateId(envelopeId, receiverId, element.id, 'signature');
|
|
const annotation = new PSPDFKit.Annotations.WidgetAnnotation({
|
|
id: id,
|
|
pageIndex: page,
|
|
formFieldName: id,
|
|
backgroundColor: PSPDFKit.Color.LIGHT_YELLOW,
|
|
blendMode: 'normal',
|
|
boundingBox: new PSPDFKit.Geometry.Rect(annotParams.signature),
|
|
})
|
|
|
|
const formField = new PSPDFKit.FormFields.SignatureFormField({
|
|
name: id,
|
|
annotationIds: PSPDFKit.Immutable.List([annotation.id]),
|
|
})
|
|
//#endregion
|
|
|
|
//#region position
|
|
const id_position = generateId(envelopeId, receiverId, element.id, 'position');
|
|
const annotation_position = new PSPDFKit.Annotations.WidgetAnnotation({
|
|
id: id_position,
|
|
pageIndex: page,
|
|
formFieldName: id_position,
|
|
backgroundColor: PSPDFKit.Color.DarkBlue,
|
|
blendMode: 'normal',
|
|
boundingBox: new PSPDFKit.Geometry.Rect(annotParams.position),
|
|
fontSize: 8
|
|
})
|
|
|
|
const formFieldPosition = new PSPDFKit.FormFields.TextFormField({
|
|
name: id_position,
|
|
annotationIds: PSPDFKit.Immutable.List([annotation_position.id]),
|
|
value: "",
|
|
readOnly: false
|
|
})
|
|
//#endregion
|
|
|
|
//#region city
|
|
const id_city = generateId(envelopeId, receiverId, element.id, 'city');
|
|
const annotation_city = new PSPDFKit.Annotations.WidgetAnnotation({
|
|
id: id_city,
|
|
pageIndex: page,
|
|
formFieldName: id_city,
|
|
backgroundColor: PSPDFKit.Color.DarkBlue,
|
|
blendMode: 'normal',
|
|
boundingBox: new PSPDFKit.Geometry.Rect(annotParams.city),
|
|
fontSize: 8
|
|
})
|
|
|
|
const formFieldCity = new PSPDFKit.FormFields.TextFormField({
|
|
name: id_city,
|
|
annotationIds: PSPDFKit.Immutable.List([annotation_city.id]),
|
|
value: "",
|
|
readOnly: false
|
|
})
|
|
//#endregion
|
|
|
|
//#region date
|
|
const id_date = generateId(envelopeId, receiverId, element.id, 'date');
|
|
const annotation_date = new PSPDFKit.Annotations.WidgetAnnotation({
|
|
id: id_date,
|
|
pageIndex: page,
|
|
formFieldName: id_date,
|
|
backgroundColor: PSPDFKit.Color.DarkBlue,
|
|
blendMode: 'normal',
|
|
boundingBox: new PSPDFKit.Geometry.Rect(annotParams.date),
|
|
fontSize: 8,
|
|
backgroundColor: PSPDFKit.Color.TRANSPARENT,
|
|
fontColor: PSPDFKit.Color.Black,
|
|
isBold: true,
|
|
required: true
|
|
})
|
|
|
|
const formFieldDate = new PSPDFKit.FormFields.TextFormField({
|
|
name: id_date,
|
|
annotationIds: PSPDFKit.Immutable.List([annotation_date.id]),
|
|
value: detailedCurrentDate(),
|
|
readOnly: true
|
|
})
|
|
//#endregion
|
|
|
|
this.markFieldAsRequired(formFieldCity);
|
|
|
|
this.markFieldAsCity(formFieldCity);
|
|
|
|
//#region date label
|
|
const id_date_label = generateId(envelopeId, receiverId, element.id, 'date_label');
|
|
const annotation_date_label = new PSPDFKit.Annotations.WidgetAnnotation({
|
|
id: id_date_label,
|
|
pageIndex: page,
|
|
formFieldName: id_date_label,
|
|
blendMode: 'normal',
|
|
boundingBox: new PSPDFKit.Geometry.Rect(annotParams.datelabel),
|
|
fontSize: 8,
|
|
backgroundColor: PSPDFKit.Color.TRANSPARENT,
|
|
fontColor: PSPDFKit.Color.Black,
|
|
isBold: true,
|
|
required: true
|
|
})
|
|
|
|
const formFieldDateLabel = new PSPDFKit.FormFields.TextFormField({
|
|
name: id_date_label,
|
|
annotationIds: PSPDFKit.Immutable.List([annotation_date_label.id]),
|
|
value: "Date",
|
|
readOnly: true
|
|
})
|
|
//#endregion
|
|
|
|
//#region city label
|
|
const id_city_label = generateId(envelopeId, receiverId, element.id, 'city_label');
|
|
const annotation_city_label = new PSPDFKit.Annotations.WidgetAnnotation({
|
|
id: id_city_label,
|
|
pageIndex: page,
|
|
formFieldName: id_city_label,
|
|
blendMode: 'normal',
|
|
boundingBox: new PSPDFKit.Geometry.Rect(annotParams.citylabel),
|
|
fontSize: 8,
|
|
backgroundColor: PSPDFKit.Color.TRANSPARENT,
|
|
fontColor: PSPDFKit.Color.Black,
|
|
isBold: true,
|
|
})
|
|
|
|
const formFieldCityLabel = new PSPDFKit.FormFields.TextFormField({
|
|
name: id_city_label,
|
|
annotationIds: PSPDFKit.Immutable.List([annotation_city_label.id]),
|
|
value: "Ort",
|
|
readOnly: true,
|
|
color: PSPDFKit.Color.BLACK
|
|
})
|
|
//#endregion
|
|
|
|
//#region position label
|
|
const id_position_label = generateId(envelopeId, receiverId, element.id, 'position_label');
|
|
const annotation_position_label = new PSPDFKit.Annotations.WidgetAnnotation({
|
|
id: id_position_label,
|
|
pageIndex: page,
|
|
formFieldName: id_position_label,
|
|
blendMode: 'normal',
|
|
boundingBox: new PSPDFKit.Geometry.Rect(annotParams.positionlabel),
|
|
fontSize: 8,
|
|
backgroundColor: PSPDFKit.Color.TRANSPARENT,
|
|
fontColor: PSPDFKit.Color.Black,
|
|
isBold: true,
|
|
})
|
|
|
|
const formFieldPositionLabel = new PSPDFKit.FormFields.TextFormField({
|
|
name: id_position_label,
|
|
annotationIds: PSPDFKit.Immutable.List([annotation_position_label.id]),
|
|
value: "Position",
|
|
readOnly: true
|
|
})
|
|
//#endregion
|
|
|
|
signatures.push(annotation)
|
|
signatures.push(formField)
|
|
signatures.push(annotation_date)
|
|
signatures.push(formFieldDate)
|
|
signatures.push(annotation_city)
|
|
signatures.push(formFieldCity)
|
|
signatures.push(annotation_position)
|
|
signatures.push(formFieldPosition)
|
|
|
|
signatures.push(annotation_date_label)
|
|
signatures.push(formFieldDateLabel)
|
|
|
|
signatures.push(annotation_city_label)
|
|
signatures.push(formFieldCityLabel)
|
|
|
|
signatures.push(annotation_position_label)
|
|
signatures.push(formFieldPositionLabel)
|
|
}
|
|
|
|
return signatures;
|
|
}
|
|
|
|
async function getAnnotations(instance) {
|
|
const array = await Promise.all(
|
|
Array.from({ length: instance.totalPageCount }).map((_, pageIndex) =>
|
|
instance.getAnnotations(pageIndex)
|
|
)
|
|
)
|
|
|
|
return array.flatMap((annotations) =>
|
|
annotations.reduce((acc, annotation) => acc.concat(annotation), [])
|
|
)
|
|
}
|
|
|
|
async function deleteAnnotations(instance) {
|
|
const allAnnotations = await getAnnotations(instance)
|
|
const pageAnnotations = allAnnotations.filter(isSignature)
|
|
//deleting all Annotations
|
|
return await instance.delete(pageAnnotations)
|
|
}
|
|
|
|
async function validateAnnotations(instance) {
|
|
const allAnnotations = await getAnnotations(instance)
|
|
const pageAnnotations = allAnnotations
|
|
.map((annotation) => {
|
|
return annotation
|
|
})
|
|
|
|
return true
|
|
}
|
|
|
|
function isSignature(annotation) {
|
|
return !!annotation.isSignature || annotation.description == 'FRAME'
|
|
}
|
|
|
|
function createImageAnnotation(boundingBox, pageIndex, imageAttachmentId, id) {
|
|
const frameAnnotation = new PSPDFKit.Annotations.ImageAnnotation({
|
|
id: id,
|
|
pageIndex: pageIndex,
|
|
isSignature: false,
|
|
readOnly: true,
|
|
locked: true,
|
|
lockedContents: true,
|
|
contentType: 'image/png',
|
|
imageAttachmentId,
|
|
description: 'FRAME',
|
|
boundingBox: boundingBox,
|
|
})
|
|
return frameAnnotation
|
|
}
|
|
|
|
async function createAnnotationFrameBlob(receiverName, receiverSignature, timestamp, width, height) {
|
|
Comp.SignatureProgress.SignedCount += 1;
|
|
const canvas = document.createElement('canvas')
|
|
const scale = 4
|
|
const fontSize = 10
|
|
|
|
canvas.width = width * scale
|
|
canvas.height = height * scale
|
|
|
|
const ctx = canvas.getContext('2d')
|
|
// This supposedly makes the lines and text less blurry
|
|
// See: https://stackoverflow.com/questions/8696631/canvas-drawings-like-lines-are-blurry
|
|
ctx.translate(0.5, 0.5)
|
|
|
|
// This also should make the lines and text less blurry
|
|
ctx.textRendering = "geometricPrecision"
|
|
|
|
const date = timestamp
|
|
const dateString = date.toLocaleString('de-DE')
|
|
|
|
const signatureLength = 100 * scale
|
|
const signatureString = receiverSignature.substring(0, 15) + "…"
|
|
|
|
ctx.beginPath()
|
|
|
|
ctx.moveTo(30 * scale, 10 * scale)
|
|
ctx.lineTo(signatureLength, 10 * scale)
|
|
|
|
ctx.moveTo(30 * scale, 10 * scale)
|
|
ctx.arcTo(10 * scale, 10 * scale, 10 * scale, 30 * scale, 20 * scale)
|
|
|
|
ctx.moveTo(10 * scale, 30 * scale)
|
|
ctx.arcTo(10 * scale, 50 * scale, 30 * scale, 50 * scale, 20 * scale)
|
|
|
|
ctx.moveTo(30 * scale, 50 * scale)
|
|
ctx.lineTo(signatureLength, 50 * scale)
|
|
|
|
ctx.strokeStyle = 'darkblue'
|
|
ctx.stroke()
|
|
|
|
ctx.fillStyle = 'black'
|
|
ctx.font = `${fontSize * scale}px sans-serif`
|
|
ctx.fillText('Signed by', 15 * scale, 10 * scale)
|
|
ctx.fillText(receiverName, 15 * scale, 60 * scale)
|
|
ctx.fillText(signatureString, 15 * scale, 70 * scale)
|
|
|
|
return new Promise((resolve) => {
|
|
canvas.toBlob((blob) => {
|
|
const url = URL.createObjectURL(blob)
|
|
resolve(url)
|
|
})
|
|
})
|
|
}
|
|
|
|
//required
|
|
requiredFieldNames = new Array()
|
|
|
|
function markFieldAsRequired(formField) {
|
|
requiredFieldNames.push(formField.name)
|
|
}
|
|
|
|
function isFieldRequired(formField) {
|
|
return requiredFieldNames.includes(formField.name)
|
|
}
|
|
|
|
//city
|
|
cityFieldNames = new Array()
|
|
|
|
function markFieldAsCity(formField) {
|
|
cityFieldNames.push(formField.name)
|
|
}
|
|
|
|
function isCityField(formField) {
|
|
return cityFieldNames.includes(formField.name)
|
|
}
|
|
|
|
function fixBase64(escapedBase64) {
|
|
return escapedBase64
|
|
.replace(/\\u002B/g, "+")
|
|
.replace(/\\u002F/g, "/")
|
|
.replace(/\\u003D/g, "=");
|
|
}
|
|
|
|
function mapSignature(iJSON) {
|
|
return {
|
|
formFields: iJSON.formFieldValues.filter(field => !field.name.includes("label")).map((field) => {
|
|
const nameParts = field.name.split('#');
|
|
field.elementId = Number(nameParts[2]);
|
|
field.name = nameParts[3];
|
|
return field;
|
|
}),
|
|
frames: iJSON.annotations.filter(annot => annot.description === 'FRAME').map((annot) => {
|
|
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], ...iJSON.annotations.filter(field => field.id.includes("signature")));
|
|
const idPartsOfPre = preElement.id.split('#');
|
|
annot.elementId = Number(idPartsOfPre[2]);
|
|
annot.name = 'frame';
|
|
annot.value = fixBase64(iJSON.attachments[annot.imageAttachmentId]?.binary);
|
|
return annot;
|
|
}),
|
|
signatures: iJSON.annotations.filter(annot => annot.isSignature).map(annot => {
|
|
const preElement = findNearest(annot, e => e.bbox[0], e => e.bbox[1], ...iJSON.annotations.filter(field => field.id.includes("signature")));
|
|
const idPartsOfPre = preElement.id.split('#');
|
|
annot.elementId = Number(idPartsOfPre[2]);
|
|
if (annot.imageAttachmentId)
|
|
annot.value = iJSON.attachments[annot.imageAttachmentId]?.binary;
|
|
else if (annot.lines && annot.strokeColor)
|
|
annot.value = JSON.stringify({
|
|
lines: annot.lines,
|
|
strokeColor: annot.strokeColor
|
|
});
|
|
else
|
|
throw new Error("Signature mapping failed: The data structure from the third-party library is incompatible or missing required fields.");
|
|
|
|
annot.name = 'signature';
|
|
return annot;
|
|
})
|
|
};
|
|
} |