TekH 9d6074874f fix(annotation): correctly assign elementId for signature annotations
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.
2025-10-21 09:25:01 +02:00

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;
})
};
}