Skip to content

Commit 474152b

Browse files
dcbrvbuch
authored andcommitted
Web and pdflib enhancements
1 parent 77ceb2f commit 474152b

28 files changed

Lines changed: 403 additions & 113 deletions

packages/examples/src/pdf-lib.js

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,17 @@ function work() {
2020
// Add a placeholder for a signature.
2121
pdflibAddPlaceholder({
2222
pdfDoc: pdfDoc,
23-
reason: 'The user is decalaring consent through JavaScript.',
23+
reason: 'The user is declaring consent through JavaScript.',
2424
contactInfo: 'signpdf@example.com',
2525
name: 'John Doe',
2626
location: 'Free Text Str., Free World',
2727
});
2828

29-
// Convert the PDF-LIB PDFDocument to Buffer
30-
pdfDoc.save({useObjectStreams: false}).then(function (pdfBytes) {
31-
var pdfWithPlaceholder = Buffer.from(pdfBytes);
32-
29+
// Get the modified PDFDocument bytes
30+
pdfDoc.save().then(function (pdfWithPlaceholderBytes) {
3331
// And finally sign the document.
3432
signpdf
35-
.sign(pdfWithPlaceholder, signer)
33+
.sign(pdfWithPlaceholderBytes, signer)
3634
.then(function (signedPdf) {
3735
// signedPdf is a Buffer of an electronically signed PDF. Store it.
3836
var targetPath = path.join(__dirname, '/../output/pdf-lib.pdf');

packages/placeholder-pdf-lib/dist/pdflibAddPlaceholder.d.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
export function pdflibAddPlaceholder({ pdfDoc, reason, contactInfo, name, location, signatureLength, byteRangePlaceholder, subFilter, widgetRect, }: InputType): void;
1+
export function pdflibAddPlaceholder({ pdfDoc, pdfPage, reason, contactInfo, name, location, signingTime, signatureLength, byteRangePlaceholder, subFilter, widgetRect, appName, }: InputType): void;
22
export type PDFDocument = import('pdf-lib').PDFDocument;
33
export type InputType = {
44
pdfDoc: PDFDocument;
5+
pdfPage: PDFPage;
56
reason: string;
67
contactInfo: string;
78
name: string;
89
location: string;
10+
signingTime?: Date;
911
signatureLength?: number;
1012
byteRangePlaceholder?: string;
1113
/**
@@ -16,5 +18,9 @@ export type InputType = {
1618
* [x1, y1, x2, y2] widget rectangle
1719
*/
1820
widgetRect?: number[];
21+
/**
22+
* Name of the application generating the signature
23+
*/
24+
appName?: string;
1925
};
2026
//# sourceMappingURL=pdflibAddPlaceholder.d.ts.map

packages/placeholder-pdf-lib/dist/pdflibAddPlaceholder.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/placeholder-pdf-lib/dist/pdflibAddPlaceholder.js

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,17 @@ var _pdfLib = require("pdf-lib");
1313
/**
1414
* @typedef {object} InputType
1515
* @property {PDFDocument} pdfDoc
16+
* @property {PDFPage} pdfPage
1617
* @property {string} reason
1718
* @property {string} contactInfo
1819
* @property {string} name
1920
* @property {string} location
21+
* @property {Date} [signingTime]
2022
* @property {number} [signatureLength]
2123
* @property {string} [byteRangePlaceholder]
2224
* @property {string} [subFilter] One of SUBFILTER_* from \@signpdf/utils
2325
* @property {number[]} [widgetRect] [x1, y1, x2, y2] widget rectangle
26+
* @property {string} [appName] Name of the application generating the signature
2427
*/
2528

2629
/**
@@ -32,21 +35,28 @@ var _pdfLib = require("pdf-lib");
3235
* @returns {void}
3336
*/
3437
const pdflibAddPlaceholder = ({
35-
pdfDoc,
38+
pdfDoc = undefined,
39+
pdfPage = undefined,
3640
reason,
3741
contactInfo,
3842
name,
3943
location,
44+
signingTime = undefined,
4045
signatureLength = _utils.DEFAULT_SIGNATURE_LENGTH,
4146
byteRangePlaceholder = _utils.DEFAULT_BYTE_RANGE_PLACEHOLDER,
4247
subFilter = _utils.SUBFILTER_ADOBE_PKCS7_DETACHED,
43-
widgetRect = [0, 0, 0, 0]
48+
widgetRect = [0, 0, 0, 0],
49+
appName = undefined
4450
}) => {
45-
const page = pdfDoc.getPage(0);
51+
if (pdfDoc === undefined && pdfPage === undefined) {
52+
throw new _utils.SignPdfError('PDFDoc or PDFPage must be set.', _utils.SignPdfError.TYPE_INPUT);
53+
}
54+
const doc = pdfDoc !== null && pdfDoc !== void 0 ? pdfDoc : pdfPage.doc;
55+
const page = pdfPage !== null && pdfPage !== void 0 ? pdfPage : doc.getPages()[0];
4656

4757
// Create a placeholder where the the last 3 parameters of the
4858
// actual range will be replaced when signing is done.
49-
const byteRange = _pdfLib.PDFArray.withContext(pdfDoc.context);
59+
const byteRange = _pdfLib.PDFArray.withContext(doc.context);
5060
byteRange.push(_pdfLib.PDFNumber.of(0));
5161
byteRange.push(_pdfLib.PDFName.of(byteRangePlaceholder));
5262
byteRange.push(_pdfLib.PDFName.of(byteRangePlaceholder));
@@ -56,52 +66,77 @@ const pdflibAddPlaceholder = ({
5666
const placeholder = _pdfLib.PDFHexString.of(String.fromCharCode(0).repeat(signatureLength));
5767

5868
// Create a signature dictionary to be referenced in the signature widget.
59-
const signatureDict = pdfDoc.context.obj({
69+
const appBuild = appName ? {
70+
App: {
71+
Name: appName
72+
}
73+
} : {};
74+
const signatureDict = doc.context.obj({
6075
Type: 'Sig',
6176
Filter: 'Adobe.PPKLite',
6277
SubFilter: subFilter,
6378
ByteRange: byteRange,
6479
Contents: placeholder,
6580
Reason: _pdfLib.PDFString.of(reason),
66-
M: _pdfLib.PDFString.fromDate(new Date()),
81+
M: _pdfLib.PDFString.fromDate(signingTime !== null && signingTime !== void 0 ? signingTime : new Date()),
6782
ContactInfo: _pdfLib.PDFString.of(contactInfo),
6883
Name: _pdfLib.PDFString.of(name),
69-
Location: _pdfLib.PDFString.of(location)
70-
}, pdfDoc.index);
71-
const signatureDictRef = pdfDoc.context.register(signatureDict);
84+
Location: _pdfLib.PDFString.of(location),
85+
Prop_Build: {
86+
Filter: {
87+
Name: 'Adobe.PPKLite'
88+
},
89+
...appBuild
90+
}
91+
});
92+
// Register signatureDict as a PDFInvalidObject to prevent PDFLib from serializing it
93+
// in an object stream.
94+
const signatureBuffer = new Uint8Array(signatureDict.sizeInBytes());
95+
signatureDict.copyBytesInto(signatureBuffer, 0);
96+
const signatureObj = _pdfLib.PDFInvalidObject.of(signatureBuffer);
97+
const signatureDictRef = doc.context.register(signatureObj);
7298

7399
// Create the signature widget
74-
const rect = _pdfLib.PDFArray.withContext(pdfDoc.context);
100+
const rect = _pdfLib.PDFArray.withContext(doc.context);
75101
widgetRect.forEach(c => rect.push(_pdfLib.PDFNumber.of(c)));
76-
const widgetDict = pdfDoc.context.obj({
102+
const apStream = doc.context.formXObject([], {
103+
BBox: widgetRect,
104+
Resources: {} // Necessary to avoid Acrobat bug (see https://stackoverflow.com/a/73011571)
105+
});
106+
107+
const widgetDict = doc.context.obj({
77108
Type: 'Annot',
78109
Subtype: 'Widget',
79110
FT: 'Sig',
80111
Rect: rect,
81112
V: signatureDictRef,
82113
T: _pdfLib.PDFString.of('Signature1'),
83114
F: _utils.ANNOTATION_FLAGS.PRINT,
84-
P: page.ref
85-
}, pdfDoc.index);
86-
const widgetDictRef = pdfDoc.context.register(widgetDict);
115+
P: page.ref,
116+
AP: {
117+
N: doc.context.register(apStream)
118+
} // Required for PDF/A compliance
119+
});
120+
121+
const widgetDictRef = doc.context.register(widgetDict);
87122

88-
// Annotate the widget on the first page
123+
// Annotate the widget on the given page
89124
let annotations = page.node.lookupMaybe(_pdfLib.PDFName.of('Annots'), _pdfLib.PDFArray);
90125
if (typeof annotations === 'undefined') {
91-
annotations = pdfDoc.context.obj([]);
126+
annotations = doc.context.obj([]);
92127
}
93128
annotations.push(widgetDictRef);
94129
page.node.set(_pdfLib.PDFName.of('Annots'), annotations);
95130

96131
// Add an AcroForm or update the existing one
97-
let acroForm = pdfDoc.catalog.lookupMaybe(_pdfLib.PDFName.of('AcroForm'), _pdfLib.PDFDict);
132+
let acroForm = doc.catalog.lookupMaybe(_pdfLib.PDFName.of('AcroForm'), _pdfLib.PDFDict);
98133
if (typeof acroForm === 'undefined') {
99134
// Need to create a new AcroForm
100-
acroForm = pdfDoc.context.obj({
135+
acroForm = doc.context.obj({
101136
Fields: []
102137
});
103-
const acroFormRef = pdfDoc.context.register(acroForm);
104-
pdfDoc.catalog.set(_pdfLib.PDFName.of('AcroForm'), acroFormRef);
138+
const acroFormRef = doc.context.register(acroForm);
139+
doc.catalog.set(_pdfLib.PDFName.of('AcroForm'), acroFormRef);
105140
}
106141

107142
/**

packages/placeholder-pdf-lib/src/pdflibAddPlaceholder.js

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,10 @@ import {
44
DEFAULT_SIGNATURE_LENGTH,
55
SIG_FLAGS,
66
SUBFILTER_ADOBE_PKCS7_DETACHED,
7+
SignPdfError,
78
} from '@signpdf/utils';
89
import {
9-
PDFArray, PDFDict, PDFHexString, PDFName, PDFNumber, PDFString,
10+
PDFArray, PDFDict, PDFHexString, PDFName, PDFNumber, PDFInvalidObject, PDFString,
1011
} from 'pdf-lib';
1112

1213
/**
@@ -16,14 +17,17 @@ import {
1617
/**
1718
* @typedef {object} InputType
1819
* @property {PDFDocument} pdfDoc
20+
* @property {PDFPage} pdfPage
1921
* @property {string} reason
2022
* @property {string} contactInfo
2123
* @property {string} name
2224
* @property {string} location
25+
* @property {Date} [signingTime]
2326
* @property {number} [signatureLength]
2427
* @property {string} [byteRangePlaceholder]
2528
* @property {string} [subFilter] One of SUBFILTER_* from \@signpdf/utils
2629
* @property {number[]} [widgetRect] [x1, y1, x2, y2] widget rectangle
30+
* @property {string} [appName] Name of the application generating the signature
2731
*/
2832

2933
/**
@@ -35,21 +39,31 @@ import {
3539
* @returns {void}
3640
*/
3741
export const pdflibAddPlaceholder = ({
38-
pdfDoc,
42+
pdfDoc = undefined,
43+
pdfPage = undefined,
3944
reason,
4045
contactInfo,
4146
name,
4247
location,
48+
signingTime = undefined,
4349
signatureLength = DEFAULT_SIGNATURE_LENGTH,
4450
byteRangePlaceholder = DEFAULT_BYTE_RANGE_PLACEHOLDER,
4551
subFilter = SUBFILTER_ADOBE_PKCS7_DETACHED,
4652
widgetRect = [0, 0, 0, 0],
53+
appName = undefined,
4754
}) => {
48-
const page = pdfDoc.getPage(0);
55+
if (pdfDoc === undefined && pdfPage === undefined) {
56+
throw new SignPdfError(
57+
'PDFDoc or PDFPage must be set.',
58+
SignPdfError.TYPE_INPUT,
59+
);
60+
}
61+
const doc = pdfDoc ?? pdfPage.doc;
62+
const page = pdfPage ?? doc.getPages()[0];
4963

5064
// Create a placeholder where the the last 3 parameters of the
5165
// actual range will be replaced when signing is done.
52-
const byteRange = PDFArray.withContext(pdfDoc.context);
66+
const byteRange = PDFArray.withContext(doc.context);
5367
byteRange.push(PDFNumber.of(0));
5468
byteRange.push(PDFName.of(byteRangePlaceholder));
5569
byteRange.push(PDFName.of(byteRangePlaceholder));
@@ -59,24 +73,38 @@ export const pdflibAddPlaceholder = ({
5973
const placeholder = PDFHexString.of(String.fromCharCode(0).repeat(signatureLength));
6074

6175
// Create a signature dictionary to be referenced in the signature widget.
62-
const signatureDict = pdfDoc.context.obj({
76+
const appBuild = appName ? {App: {Name: appName}} : {};
77+
const signatureDict = doc.context.obj({
6378
Type: 'Sig',
6479
Filter: 'Adobe.PPKLite',
6580
SubFilter: subFilter,
6681
ByteRange: byteRange,
6782
Contents: placeholder,
6883
Reason: PDFString.of(reason),
69-
M: PDFString.fromDate(new Date()),
84+
M: PDFString.fromDate(signingTime ?? new Date()),
7085
ContactInfo: PDFString.of(contactInfo),
7186
Name: PDFString.of(name),
7287
Location: PDFString.of(location),
73-
}, pdfDoc.index);
74-
const signatureDictRef = pdfDoc.context.register(signatureDict);
88+
Prop_Build: {
89+
Filter: {Name: 'Adobe.PPKLite'},
90+
...appBuild,
91+
},
92+
});
93+
// Register signatureDict as a PDFInvalidObject to prevent PDFLib from serializing it
94+
// in an object stream.
95+
const signatureBuffer = new Uint8Array(signatureDict.sizeInBytes());
96+
signatureDict.copyBytesInto(signatureBuffer, 0);
97+
const signatureObj = PDFInvalidObject.of(signatureBuffer);
98+
const signatureDictRef = doc.context.register(signatureObj);
7599

76100
// Create the signature widget
77-
const rect = PDFArray.withContext(pdfDoc.context);
101+
const rect = PDFArray.withContext(doc.context);
78102
widgetRect.forEach((c) => rect.push(PDFNumber.of(c)));
79-
const widgetDict = pdfDoc.context.obj({
103+
const apStream = doc.context.formXObject([], {
104+
BBox: widgetRect,
105+
Resources: {}, // Necessary to avoid Acrobat bug (see https://stackoverflow.com/a/73011571)
106+
});
107+
const widgetDict = doc.context.obj({
80108
Type: 'Annot',
81109
Subtype: 'Widget',
82110
FT: 'Sig',
@@ -85,24 +113,25 @@ export const pdflibAddPlaceholder = ({
85113
T: PDFString.of('Signature1'),
86114
F: ANNOTATION_FLAGS.PRINT,
87115
P: page.ref,
88-
}, pdfDoc.index);
89-
const widgetDictRef = pdfDoc.context.register(widgetDict);
116+
AP: {N: doc.context.register(apStream)}, // Required for PDF/A compliance
117+
});
118+
const widgetDictRef = doc.context.register(widgetDict);
90119

91-
// Annotate the widget on the first page
120+
// Annotate the widget on the given page
92121
let annotations = page.node.lookupMaybe(PDFName.of('Annots'), PDFArray);
93122
if (typeof annotations === 'undefined') {
94-
annotations = pdfDoc.context.obj([]);
123+
annotations = doc.context.obj([]);
95124
}
96125
annotations.push(widgetDictRef);
97126
page.node.set(PDFName.of('Annots'), annotations);
98127

99128
// Add an AcroForm or update the existing one
100-
let acroForm = pdfDoc.catalog.lookupMaybe(PDFName.of('AcroForm'), PDFDict);
129+
let acroForm = doc.catalog.lookupMaybe(PDFName.of('AcroForm'), PDFDict);
101130
if (typeof acroForm === 'undefined') {
102131
// Need to create a new AcroForm
103-
acroForm = pdfDoc.context.obj({Fields: []});
104-
const acroFormRef = pdfDoc.context.register(acroForm);
105-
pdfDoc.catalog.set(PDFName.of('AcroForm'), acroFormRef);
132+
acroForm = doc.context.obj({Fields: []});
133+
const acroFormRef = doc.context.register(acroForm);
134+
doc.catalog.set(PDFName.of('AcroForm'), acroFormRef);
106135
}
107136

108137
/**

0 commit comments

Comments
 (0)