Compare commits

...

2 Commits

Author SHA1 Message Date
Lucas Smith b0248c20eb v2.2.8 2025-12-16 16:04:07 +11:00
Lucas Smith f129968968 fix: ensure PDF form appearance streams have required /Subtype /Form entry (#2328)
When flattening PDF forms, some appearance streams lack the required
/Subtype /Form dictionary entry needed when used as XObjects. This
causes
corruption in Adobe Reader which fails to render these flattened fields.

Per PDF spec, Form XObject streams require:
- /Subtype /Form (required)
- /FormType 1 (optional)

The normalizeAppearanceStream function ensures these entries exist
before
adding appearance streams as XObjects to the page content stream.

Fixes rendering issues where flattened fields don't display in PDF
viewers.
2025-12-16 16:00:11 +11:00
4 changed files with 40 additions and 5 deletions
+1 -1
View File
@@ -107,5 +107,5 @@
"vite-plugin-babel-macros": "^1.0.6", "vite-plugin-babel-macros": "^1.0.6",
"vite-tsconfig-paths": "^5.1.4" "vite-tsconfig-paths": "^5.1.4"
}, },
"version": "2.2.7" "version": "2.2.8"
} }
+3 -3
View File
@@ -1,12 +1,12 @@
{ {
"name": "@documenso/root", "name": "@documenso/root",
"version": "2.2.7", "version": "2.2.8",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@documenso/root", "name": "@documenso/root",
"version": "2.2.7", "version": "2.2.8",
"hasInstallScript": true, "hasInstallScript": true,
"workspaces": [ "workspaces": [
"apps/*", "apps/*",
@@ -109,7 +109,7 @@
}, },
"apps/remix": { "apps/remix": {
"name": "@documenso/remix", "name": "@documenso/remix",
"version": "2.2.7", "version": "2.2.8",
"dependencies": { "dependencies": {
"@cantoo/pdf-lib": "^2.5.3", "@cantoo/pdf-lib": "^2.5.3",
"@documenso/api": "*", "@documenso/api": "*",
+1 -1
View File
@@ -5,7 +5,7 @@
"apps/*", "apps/*",
"packages/*" "packages/*"
], ],
"version": "2.2.7", "version": "2.2.8",
"scripts": { "scripts": {
"postinstall": "patch-package", "postinstall": "patch-package",
"build": "turbo run build", "build": "turbo run build",
@@ -4,8 +4,10 @@ import {
PDFDict, PDFDict,
type PDFDocument, type PDFDocument,
PDFName, PDFName,
PDFNumber,
PDFRadioGroup, PDFRadioGroup,
PDFRef, PDFRef,
PDFStream,
drawObject, drawObject,
popGraphicsState, popGraphicsState,
pushGraphicsState, pushGraphicsState,
@@ -103,6 +105,36 @@ const getAppearanceRefForWidget = (field: PDFField, widget: PDFWidgetAnnotation)
} }
}; };
/**
* Ensures that an appearance stream has the required dictionary entries to be
* used as a Form XObject. Some PDFs have appearance streams that are missing
* the /Subtype /Form entry, which causes Adobe Reader to fail to render them.
*
* Per PDF spec, a Form XObject stream requires:
* - /Subtype /Form (required)
* - /BBox (required, but should already exist for appearance streams)
* - /FormType 1 (optional, defaults to 1)
*/
const normalizeAppearanceStream = (document: PDFDocument, appearanceRef: PDFRef) => {
const appearanceStream = document.context.lookup(appearanceRef);
if (!(appearanceStream instanceof PDFStream)) {
return;
}
const dict = appearanceStream.dict;
// Ensure /Subtype /Form is set (required for XObject Form)
if (!dict.has(PDFName.of('Subtype'))) {
dict.set(PDFName.of('Subtype'), PDFName.of('Form'));
}
// Ensure /FormType is set (optional, but good practice)
if (!dict.has(PDFName.of('FormType'))) {
dict.set(PDFName.of('FormType'), PDFNumber.of(1));
}
};
const flattenWidget = (document: PDFDocument, field: PDFField, widget: PDFWidgetAnnotation) => { const flattenWidget = (document: PDFDocument, field: PDFField, widget: PDFWidgetAnnotation) => {
try { try {
const page = getPageForWidget(document, widget); const page = getPageForWidget(document, widget);
@@ -117,6 +149,9 @@ const flattenWidget = (document: PDFDocument, field: PDFField, widget: PDFWidget
return; return;
} }
// Ensure the appearance stream has required XObject Form dictionary entries
normalizeAppearanceStream(document, appearanceRef);
const xObjectKey = page.node.newXObject('FlatWidget', appearanceRef); const xObjectKey = page.node.newXObject('FlatWidget', appearanceRef);
const rectangle = widget.getRectangle(); const rectangle = widget.getRectangle();