@@ -115,6 +115,164 @@ const StyledDiv = styled.div`
115115` ;
116116```
117117
118+ ### PDF Signing
119+
120+ The module supports digitally signing PDF documents using P12/PFX certificates. The ` PdfSigner ` service is exported from
121+ the module and can be used standalone, or you can use the convenient ` asSignedBuffer ` / ` asSignedStream ` methods returned
122+ by ` generatePdf ` .
123+
124+ #### Signing via ` generatePdf `
125+
126+ The ` generatePdf ` method returns ` asSignedBuffer ` and ` asSignedStream ` in addition to the unsigned variants. Both accept
127+ a ` SignPdfOptions ` object:
128+
129+ ``` ts
130+ import { PdfRenderer , SignPdfOptions } from " @leancodepl/pdf-renderer" ;
131+ import { readFileSync } from " fs" ;
132+
133+ @Controller (" pdf-renderer" )
134+ export class AppController {
135+ constructor (private readonly pdfRenderer : PdfRenderer ) {}
136+
137+ @Get (" signedPdf" )
138+ async signedPdf(@Res () res : Response ) {
139+ const signOptions: SignPdfOptions = {
140+ p12Buffer: readFileSync (" /path/to/certificate.p12" ),
141+ passphrase: " cert-password" ,
142+ name: " John Doe" ,
143+ reason: " Document approval" ,
144+ location: " Warsaw, Poland" ,
145+ contactInfo: " john@example.com" ,
146+ };
147+
148+ const stream = await this .pdfRenderer
149+ .generatePdf ({ element: <SampleComponent />, fonts: [OpenSansRegular ] })
150+ .asSignedStream (signOptions );
151+
152+ res .header (" Content-Type" , " application/pdf" );
153+ res .header (" Content-Disposition" , ' attachment; filename="signed.pdf"' );
154+
155+ stream .pipe (res );
156+ }
157+ }
158+ ```
159+
160+ #### ` SignPdfOptions `
161+
162+ ``` ts
163+ type SignPdfOptions = {
164+ /** P12/PFX certificate bundle as a Buffer */
165+ p12Buffer: Buffer ;
166+ /** Passphrase for the P12 certificate */
167+ passphrase? : string ;
168+ /** Reason for signing */
169+ reason? : string ;
170+ /** Contact information of the signer */
171+ contactInfo? : string ;
172+ /** Location where the document was signed */
173+ location? : string ;
174+ /** Name of the signer */
175+ name? : string ;
176+ /**
177+ * Maximum length (in bytes) reserved for the PKCS#7 signature.
178+ * Increase this if signing fails due to a large certificate chain.
179+ * @default 8192
180+ */
181+ signatureMaxLength? : number ;
182+ /**
183+ * Custom [x1, y1, x2, y2] rectangle for the visible signature widget.
184+ * When not provided, the signature is placed at the top of the last page,
185+ * stretched across the full width.
186+ * Set to [0, 0, 0, 0] to make the signature invisible.
187+ */
188+ widgetRect? : [number , number , number , number ];
189+ /**
190+ * When true, uses the ETSI.CAdES.detached SubFilter for PAdES
191+ * (PDF Advanced Electronic Signatures) instead of the default
192+ * adbe.pkcs7.detached.
193+ * @default false
194+ */
195+ pades? : boolean ;
196+ /**
197+ * Label text shown above the signer name in the visible signature widget.
198+ * @default " Digitally signed by"
199+ */
200+ signatureLabel? : string ;
201+ /**
202+ * Pre-rendered PNG image buffer for a custom signature appearance.
203+ * When provided, this image is embedded in the signature widget instead of
204+ * the default operator-based text appearance.
205+ */
206+ signatureImage? : Buffer ;
207+ };
208+ ```
209+
210+ #### Visible signature appearance
211+
212+ By default, the signature widget renders a text-based appearance showing the signer's name, date, and reason. You can
213+ customize this in three ways:
214+
215+ 1 . ** Default appearance** - a built-in text layout with the signer name, label, date, and optional reason.
216+ 2 . ** Custom React component** - pass a ` signature ` component to ` generatePdf ` . It receives ` SignatureAppearanceProps `
217+ and is rendered to a PNG image that replaces the default text appearance.
218+ 3 . ** Pre-rendered image** - pass a ` signatureImage ` buffer (PNG) in ` SignPdfOptions ` to use an arbitrary image.
219+
220+ ##### Custom signature component example
221+
222+ ``` ts
223+ import { SignatureAppearanceProps } from " @leancodepl/pdf-renderer" ;
224+
225+ const CustomSignature: React .FC <SignatureAppearanceProps > = ({ name , date , reason }) => (
226+ < div style = {{ padding : 10 , border : " 1px solid gray" , fontFamily : " Open Sans" }}>
227+ <strong >{name }< / strong >
228+ <div >{date }< / div >
229+ {reason && <div >Reason : {reason }</div >}
230+ < / div >
231+ );
232+
233+ // Pass it to generatePdf:
234+ const stream = await pdfRenderer
235+ .generatePdf ({
236+ element: <SampleComponent />,
237+ fonts: [OpenSansRegular ],
238+ signature: CustomSignature ,
239+ signatureFonts: [OpenSansRegular ], // optional, falls back to fonts
240+ })
241+ .asSignedStream (signOptions );
242+ ```
243+
244+ #### Using ` PdfSigner ` directly
245+
246+ You can also inject and use the ` PdfSigner ` service directly for more control:
247+
248+ ``` ts
249+ import { PdfSigner , SignPdfOptions } from " @leancodepl/pdf-renderer" ;
250+
251+ @Injectable ()
252+ export class MyService {
253+ constructor (private readonly pdfSigner : PdfSigner ) {}
254+
255+ async signExistingPdf(pdfBuffer : Buffer , options : SignPdfOptions ): Promise <Buffer > {
256+ return this .pdfSigner .sign (pdfBuffer , options );
257+ }
258+ }
259+ ```
260+
261+ The ` sign ` method appends a new page with the signature widget and applies the digital signature.
262+
263+ #### Invisible signatures
264+
265+ To create a digitally signed PDF without a visible signature widget, set ` widgetRect ` to ` [0, 0, 0, 0] ` :
266+
267+ ``` ts
268+ const signedBuffer = await pdfRenderer
269+ .generatePdf ({ element: <SampleComponent /> })
270+ .asSignedBuffer ({
271+ ... signOptions ,
272+ widgetRect: [0 , 0 , 0 , 0 ],
273+ });
274+ ```
275+
118276### Styles
119277
120278For styling your PDF you should use styled components.
0 commit comments