mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 08:13:56 +10:00
This PR is handles the changes required to support envelopes. The new envelope editor/signing page will be hidden during release. The core changes here is to migrate the documents and templates model to a centralized envelopes model. Even though Documents and Templates are removed, from the user perspective they will still exist as we remap envelopes to documents and templates.
136 lines
3.2 KiB
TypeScript
136 lines
3.2 KiB
TypeScript
import type { PDFField, PDFWidgetAnnotation } from '@cantoo/pdf-lib';
|
|
import {
|
|
PDFCheckBox,
|
|
PDFDict,
|
|
type PDFDocument,
|
|
PDFName,
|
|
PDFRadioGroup,
|
|
PDFRef,
|
|
drawObject,
|
|
popGraphicsState,
|
|
pushGraphicsState,
|
|
rotateInPlace,
|
|
translate,
|
|
} from '@cantoo/pdf-lib';
|
|
import fontkit from '@pdf-lib/fontkit';
|
|
|
|
import { NEXT_PUBLIC_WEBAPP_URL } from '../../constants/app';
|
|
|
|
export const removeOptionalContentGroups = (document: PDFDocument) => {
|
|
const context = document.context;
|
|
const catalog = context.lookup(context.trailerInfo.Root);
|
|
if (catalog instanceof PDFDict) {
|
|
catalog.delete(PDFName.of('OCProperties'));
|
|
}
|
|
};
|
|
|
|
export const flattenForm = async (document: PDFDocument) => {
|
|
removeOptionalContentGroups(document);
|
|
|
|
const form = document.getForm();
|
|
|
|
const fontNoto = await fetch(`${NEXT_PUBLIC_WEBAPP_URL()}/fonts/noto-sans.ttf`).then(
|
|
async (res) => res.arrayBuffer(),
|
|
);
|
|
|
|
document.registerFontkit(fontkit);
|
|
|
|
const font = await document.embedFont(fontNoto);
|
|
|
|
form.updateFieldAppearances(font);
|
|
|
|
for (const field of form.getFields()) {
|
|
for (const widget of field.acroField.getWidgets()) {
|
|
flattenWidget(document, field, widget);
|
|
}
|
|
|
|
try {
|
|
form.removeField(field);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
};
|
|
|
|
const getPageForWidget = (document: PDFDocument, widget: PDFWidgetAnnotation) => {
|
|
const pageRef = widget.P();
|
|
|
|
let page = document.getPages().find((page) => page.ref === pageRef);
|
|
|
|
if (!page) {
|
|
const widgetRef = document.context.getObjectRef(widget.dict);
|
|
|
|
if (!widgetRef) {
|
|
return null;
|
|
}
|
|
|
|
page = document.findPageForAnnotationRef(widgetRef);
|
|
|
|
if (!page) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
return page;
|
|
};
|
|
|
|
const getAppearanceRefForWidget = (field: PDFField, widget: PDFWidgetAnnotation) => {
|
|
try {
|
|
const normalAppearance = widget.getNormalAppearance();
|
|
let normalAppearanceRef: PDFRef | null = null;
|
|
|
|
if (normalAppearance instanceof PDFRef) {
|
|
normalAppearanceRef = normalAppearance;
|
|
}
|
|
|
|
if (
|
|
normalAppearance instanceof PDFDict &&
|
|
(field instanceof PDFCheckBox || field instanceof PDFRadioGroup)
|
|
) {
|
|
const value = field.acroField.getValue();
|
|
const ref = normalAppearance.get(value) ?? normalAppearance.get(PDFName.of('Off'));
|
|
|
|
if (ref instanceof PDFRef) {
|
|
normalAppearanceRef = ref;
|
|
}
|
|
}
|
|
|
|
return normalAppearanceRef;
|
|
} catch (error) {
|
|
console.error(error);
|
|
|
|
return null;
|
|
}
|
|
};
|
|
|
|
const flattenWidget = (document: PDFDocument, field: PDFField, widget: PDFWidgetAnnotation) => {
|
|
try {
|
|
const page = getPageForWidget(document, widget);
|
|
|
|
if (!page) {
|
|
return;
|
|
}
|
|
|
|
const appearanceRef = getAppearanceRefForWidget(field, widget);
|
|
|
|
if (!appearanceRef) {
|
|
return;
|
|
}
|
|
|
|
const xObjectKey = page.node.newXObject('FlatWidget', appearanceRef);
|
|
|
|
const rectangle = widget.getRectangle();
|
|
const operators = [
|
|
pushGraphicsState(),
|
|
translate(rectangle.x, rectangle.y),
|
|
...rotateInPlace({ ...rectangle, rotation: 0 }),
|
|
drawObject(xObjectKey),
|
|
popGraphicsState(),
|
|
].filter((op) => !!op);
|
|
|
|
page.pushOperators(...operators);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
};
|