@@ -4,9 +4,10 @@ import {
44 DEFAULT_SIGNATURE_LENGTH ,
55 SIG_FLAGS ,
66 SUBFILTER_ADOBE_PKCS7_DETACHED ,
7+ SignPdfError ,
78} from '@signpdf/utils' ;
89import {
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 */
3741export 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