mirror of
https://github.com/documenso/documenso.git
synced 2025-11-13 16:23:06 +10:00
During our field rework that makes fields appear more accurately between signing and the completed pdf we swapped to using text fields. Unfortunately as part of that we dropped using the Noto font for the text field causing ANSI encoding issues when encountering certain characters. This change restores the font and handles a nasty issue we had with our form flattening reverting our selected font.
136 lines
3.2 KiB
TypeScript
136 lines
3.2 KiB
TypeScript
import fontkit from '@pdf-lib/fontkit';
|
|
import type { PDFField, PDFWidgetAnnotation } from 'pdf-lib';
|
|
import {
|
|
PDFCheckBox,
|
|
PDFDict,
|
|
type PDFDocument,
|
|
PDFName,
|
|
PDFRadioGroup,
|
|
PDFRef,
|
|
drawObject,
|
|
popGraphicsState,
|
|
pushGraphicsState,
|
|
rotateInPlace,
|
|
translate,
|
|
} from 'pdf-lib';
|
|
|
|
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);
|
|
}
|
|
};
|