Files
documenso/packages/lib/server-only/pdf/generate-partial-signed-pdf.ts
2026-05-08 16:04:22 +10:00

77 lines
2.0 KiB
TypeScript

import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature';
import { PDF } from '@libpdf/core';
import { groupBy } from 'remeda';
import { insertFieldInPDFV2 } from './insert-field-in-pdf-v2';
type GeneratePartialSignedPdfOptions = {
pdfData: Uint8Array;
fields: FieldWithSignature[];
};
/**
* Generates a PDF with all currently-inserted fields burned in. Used to serve
* partially signed envelopes during the `PENDING` window before the seal job
* has had a chance to produce the final sealed PDF.
*
* No PKI signature, no certificate page, no audit log appendix - this is a
* preview of the in-progress envelope, not a final executed document.
*/
export const generatePartialSignedPdf = async ({ pdfData, fields }: GeneratePartialSignedPdfOptions) => {
const pdfDoc = await PDF.load(pdfData);
pdfDoc.flattenAll();
pdfDoc.upgradeVersion('1.7');
const fieldsGroupedByPage = groupBy(fields, (field) => field.page);
for (const [pageNumber, pageFields] of Object.entries(fieldsGroupedByPage)) {
const page = pdfDoc.getPage(Number(pageNumber) - 1);
if (!page) {
throw new Error(`Page ${pageNumber} does not exist`);
}
const pageWidth = page.width;
const pageHeight = page.height;
const overlayBytes = await insertFieldInPDFV2({
pageWidth,
pageHeight,
fields: pageFields,
});
const overlayPdf = await PDF.load(overlayBytes);
const embeddedPage = await pdfDoc.embedPage(overlayPdf, 0);
let translateX = 0;
let translateY = 0;
switch (page.rotation) {
case 90:
translateX = pageHeight;
translateY = 0;
break;
case 180:
translateX = pageWidth;
translateY = pageHeight;
break;
case 270:
translateX = 0;
translateY = pageWidth;
break;
}
page.drawPage(embeddedPage, {
x: translateX,
y: translateY,
rotate: {
angle: page.rotation,
},
});
}
pdfDoc.flattenAll();
return await pdfDoc.save({ useXRefStream: true });
};