1 /*
2  * Copyright 2003, 2004 by Paulo Soares.
3  *
4  *
5  * The Original Code is 'iText, a free JAVA-PDF library'.
6  *
7  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
8  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
9  * All Rights Reserved.
10  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
11  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
12  *
13  * Contributor(s): all the names of the contributors are added in the source code
14  * where applicable.
15  *
16  *
17  * This library is free software; you can redistribute it and/or
18  * modify it under the terms of the GNU Library General Public
19  * License as published by the Free Software Foundation; either
20  * version 2 of the License, or (at your option) any later version.
21  *
22  * This library is distributed in the hope that it will be useful,
23  * but WITHOUT ANY WARRANTY; without even the implied warranty of
24  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
25  * Library General Public License for more details.
26  *
27  * You should have received a copy of the GNU Library General Public
28  * License along with this library; if not, write to the
29  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
30  * Boston, MA  02110-1301, USA.
31  *
32  *
33  * This library is free software; you can redistribute it and/or
34  * modify it under the terms of the GNU Library General Public
35  * License as published by the Free Software Foundation; either
36  * version 2 of the License, or (at your option) any later version.
37  *
38  * This library is distributed in the hope that it will be useful,
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
41  * Library General Public License for more details.
42  *
43  * You should have received a copy of the GNU Library General Public
44  * License along with this library; if not, write to the
45  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
46  * Boston, MA  02110-1301, USA.
47  *
48  *
49  * If you didn't download this code from the following link, you should check if
50  * you aren't using an obsolete version:
51  * http://www.lowagie.com/iText/
52  */
53 package com.gitlab.pdftk_java.com.lowagie.text.pdf;
54 
55 import java.security.SignatureException;
56 import java.io.OutputStream;
57 import java.io.FileOutputStream;
58 import java.io.IOException;
59 // ssteward omit: import java.io.EOFException;
60 // ssteward omit: import java.io.RandomAccessFile;
61 import java.io.File;
62 import java.io.InputStream;
63 import com.gitlab.pdftk_java.com.lowagie.text.DocumentException;
64 import com.gitlab.pdftk_java.com.lowagie.text.ExceptionConverter;
65 // ssteward omit: import com.gitlab.pdftk_java.com.lowagie.text.DocWriter;
66 import com.gitlab.pdftk_java.com.lowagie.text.Rectangle;
67 // import com.gitlab.pdftk_java.com.lowagie.text.Image; ssteward: dropped in 1.44
68 import java.util.HashMap;
69 import java.util.List;
70 // ssteward omit: import java.util.Iterator;
71 
72 /** Applies extra content to the pages of a PDF document.
73  * This extra content can be all the objects allowed in PdfContentByte
74  * including pages from other Pdfs. The original PDF will keep
75  * all the interactive elements including bookmarks, links and form fields.
76  * <p>
77  * It is also possible to change the field values and to
78  * flatten them. New fields can be added but not flattened.
79  * @author Paulo Soares (psoares@consiste.pt)
80  */
81 public class PdfStamper {
82     /**
83      * The writer
84      */
85     protected PdfStamperImp stamper = null;
86     private HashMap moreInfo = null;
87     private boolean hasSignature = false;
88     private PdfSignatureAppearance sigApp = null;
89 
90     /** Starts the process of adding extra content to an existing PDF
91      * document.
92      * @param reader the original document. It cannot be reused
93      * @param os the output stream
94      * @throws DocumentException on error
95      * @throws IOException on error
96      */
PdfStamper(PdfReader reader, OutputStream os)97     public PdfStamper(PdfReader reader, OutputStream os) throws DocumentException, IOException {
98         stamper = new PdfStamperImp(reader, os, '\0', false);
99     }
100 
101     /**
102      * Starts the process of adding extra content to an existing PDF
103      * document.
104      * @param reader the original document. It cannot be reused
105      * @param os the output stream
106      * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
107      * document
108      * @throws DocumentException on error
109      * @throws IOException on error
110      */
PdfStamper(PdfReader reader, OutputStream os, char pdfVersion)111     public PdfStamper(PdfReader reader, OutputStream os, char pdfVersion) throws DocumentException, IOException {
112         stamper = new PdfStamperImp(reader, os, pdfVersion, false);
113     }
114 
115     /**
116      * Starts the process of adding extra content to an existing PDF
117      * document, possibly as a new revision.
118      * @param reader the original document. It cannot be reused
119      * @param os the output stream
120      * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
121      * document
122      * @param append if <CODE>true</CODE> appends the document changes as a new revision. This is
123      * only useful for multiple signatures as nothing is gained in speed or memory
124      * @throws DocumentException on error
125      * @throws IOException on error
126      */
PdfStamper(PdfReader reader, OutputStream os, char pdfVersion, boolean append)127     public PdfStamper(PdfReader reader, OutputStream os, char pdfVersion, boolean append) throws DocumentException, IOException {
128         stamper = new PdfStamperImp(reader, os, pdfVersion, append);
129     }
130 
131     /** Gets the optional <CODE>String</CODE> map to add or change values in
132      * the info dictionary.
133      * @return the map or <CODE>null</CODE>
134      *
135      */
getMoreInfo()136     public HashMap getMoreInfo() {
137         return this.moreInfo;
138     }
139 
140     /** An optional <CODE>String</CODE> map to add or change values in
141      * the info dictionary. Entries with <CODE>null</CODE>
142      * values delete the key in the original info dictionary
143      * @param moreInfo additional entries to the info dictionary
144      *
145      */
setMoreInfo(HashMap moreInfo)146     public void setMoreInfo(HashMap moreInfo) {
147         this.moreInfo = moreInfo;
148     }
149 
150     /**
151      * Inserts a blank page. All the pages above and including <CODE>pageNumber</CODE> will
152      * be shifted up. If <CODE>pageNumber</CODE> is bigger than the total number of pages
153      * the new page will be the last one.
154      * @param pageNumber the page number position where the new page will be inserted
155      * @param mediabox the size of the new page
156      */
insertPage(int pageNumber, Rectangle mediabox)157     public void insertPage(int pageNumber, Rectangle mediabox) {
158         stamper.insertPage(pageNumber, mediabox);
159     }
160 
161     /**
162      * Gets the signing instance. The appearances and other parameters can the be set.
163      * @return the signing instance
164      */
getSignatureAppearance()165     public PdfSignatureAppearance getSignatureAppearance() {
166         return sigApp;
167     }
168     /* ssteward omit:
169     private String getNewSigName() {
170         AcroFields af = getAcroFields();
171         String name = "Signature";
172         int step = 0;
173         boolean found = false;
174         while (!found) {
175             ++step;
176             String n1 = name + step;
177             if (af.getFieldItem(n1) != null)
178                 continue;
179             n1 += ".";
180             found = true;
181             for (Iterator it = af.getFields().keySet().iterator(); it.hasNext();) {
182                 String fn = (String)it.next();
183                 if (fn.startsWith(n1)) {
184                     found = false;
185                     break;
186                 }
187             }
188         }
189         name += step;
190         return name;
191     }
192     */
193 
194     /**
195      * Closes the document. No more content can be written after the
196      * document is closed.
197      * <p>
198      * If closing a signed document with an external signature the closing must be done
199      * in the <CODE>PdfSignatureAppearance</CODE> instance.
200      * @throws DocumentException on error
201      * @throws IOException on error
202      */
close()203     public void close() throws DocumentException, IOException {
204         if (!hasSignature) {
205             stamper.close(moreInfo);
206             return;
207         }
208         sigApp.preClose();
209         PdfSigGenericPKCS sig = sigApp.getSigStandard();
210         PdfLiteral lit = (PdfLiteral)sig.get(PdfName.CONTENTS);
211         int totalBuf = (lit.getPosLength() - 2) / 2;
212         byte buf[] = new byte[8192];
213         int n;
214         InputStream inp = sigApp.getRangeStream();
215         try {
216             while ((n = inp.read(buf)) > 0) {
217                 sig.getSigner().update(buf, 0, n);
218             }
219         }
220         catch (SignatureException se) {
221             throw new ExceptionConverter(se);
222         }
223         buf = new byte[totalBuf];
224         byte[] bsig = sig.getSignerContents();
225         System.arraycopy(bsig, 0, buf, 0, bsig.length);
226         PdfString str = new PdfString(buf);
227         str.setHexWriting(true);
228         PdfDictionary dic = new PdfDictionary();
229         dic.put(PdfName.CONTENTS, str);
230         sigApp.close(dic);
231         stamper.reader.close();
232     }
233 
234     /* ssteward omit:
235     private static int indexArray(byte bout[], int position, String search) {
236         byte ss[] = PdfEncodings.convertToBytes(search, null);
237         while (true) {
238             int k;
239             for (k = 0; k < ss.length; ++k) {
240                 if (ss[k] != bout[position + k])
241                     break;
242             }
243             if (k == ss.length)
244                 return position;
245             ++position;
246         }
247     }
248 
249     private static int indexFile(RandomAccessFile raf, int position, String search) throws IOException {
250         byte ss[] = PdfEncodings.convertToBytes(search, null);
251         while (true) {
252             raf.seek(position);
253             int k;
254             for (k = 0; k < ss.length; ++k) {
255                 int b = raf.read();
256                 if (b < 0)
257                     throw new EOFException("Unexpected EOF");
258                 if (ss[k] != (byte)b)
259                     break;
260             }
261             if (k == ss.length)
262                 return position;
263             ++position;
264         }
265     }
266     */
267 
268     /** Gets a <CODE>PdfContentByte</CODE> to write under the page of
269      * the original document.
270      * @param pageNum the page number where the extra content is written
271      * @return a <CODE>PdfContentByte</CODE> to write under the page of
272      * the original document
273      */
getUnderContent(int pageNum)274     public PdfContentByte getUnderContent(int pageNum) {
275         return stamper.getUnderContent(pageNum);
276     }
277 
278     /** Gets a <CODE>PdfContentByte</CODE> to write over the page of
279      * the original document.
280      * @param pageNum the page number where the extra content is written
281      * @return a <CODE>PdfContentByte</CODE> to write over the page of
282      * the original document
283      */
getOverContent(int pageNum)284     public PdfContentByte getOverContent(int pageNum) {
285         return stamper.getOverContent(pageNum);
286     }
287 
288     /** Checks if the content is automatically adjusted to compensate
289      * the original page rotation.
290      * @return the auto-rotation status
291      */
isRotateContents()292     public boolean isRotateContents() {
293         return stamper.isRotateContents();
294     }
295 
296     /** Flags the content to be automatically adjusted to compensate
297      * the original page rotation. The default is <CODE>true</CODE>.
298      * @param rotateContents <CODE>true</CODE> to set auto-rotation, <CODE>false</CODE>
299      * otherwise
300      */
setRotateContents(boolean rotateContents)301     public void setRotateContents(boolean rotateContents) {
302         stamper.setRotateContents(rotateContents);
303     }
304 
305     /** Sets the encryption options for this document. The userPassword and the
306      *  ownerPassword can be null or have zero length. In this case the ownerPassword
307      *  is replaced by a random string. The open permissions for the document can be
308      *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
309      *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
310      *  The permissions can be combined by ORing them.
311      * @param userPassword the user password. Can be null or empty
312      * @param ownerPassword the owner password. Can be null or empty
313      * @param permissions the user permissions
314      * @param strength128Bits <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length
315      * @throws DocumentException if anything was already written to the output
316      */
317     // legacy
setEncryption(byte userPassword[], byte ownerPassword[], int permissions, boolean strength128Bits)318     public void setEncryption(byte userPassword[], byte ownerPassword[], int permissions, boolean strength128Bits) throws DocumentException {
319         if (stamper.isAppend())
320             throw new DocumentException("Append mode does not support changing the encryption status.");
321         if (stamper.isContentWritten())
322             throw new DocumentException("Content was already written to the output.");
323         stamper.setEncryption(userPassword, ownerPassword, permissions, strength128Bits ? PdfWriter.STANDARD_ENCRYPTION_128 : PdfWriter.STANDARD_ENCRYPTION_40);
324     }
325 
326     /** Sets the encryption options for this document. The userPassword and the
327      *  ownerPassword can be null or have zero length. In this case the ownerPassword
328      *  is replaced by a random string. The open permissions for the document can be
329      *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
330      *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
331      *  The permissions can be combined by ORing them.
332      * @param userPassword the user password. Can be null or empty
333      * @param ownerPassword the owner password. Can be null or empty
334      * @param permissions the user permissions
335      * @param encryptionType the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128 or ENCRYPTION_AES128.
336      * Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext
337      * @throws DocumentException if the document is already open
338      */
339     // preferred
setEncryption(byte userPassword[], byte ownerPassword[], int permissions, int encryptionType)340     public void setEncryption(byte userPassword[], byte ownerPassword[], int permissions, int encryptionType) throws DocumentException {
341         if (stamper.isAppend())
342             throw new DocumentException("Append mode does not support changing the encryption status.");
343         if (stamper.isContentWritten())
344             throw new DocumentException("Content was already written to the output.");
345         stamper.setEncryption(userPassword, ownerPassword, permissions, encryptionType);
346     }
347 
348     /**
349      * Sets the encryption options for this document. The userPassword and the
350      *  ownerPassword can be null or have zero length. In this case the ownerPassword
351      *  is replaced by a random string. The open permissions for the document can be
352      *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
353      *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
354      *  The permissions can be combined by ORing them.
355      * @param strength <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length
356      * @param userPassword the user password. Can be null or empty
357      * @param ownerPassword the owner password. Can be null or empty
358      * @param permissions the user permissions
359      * @throws DocumentException if anything was already written to the output
360      */
361     /*
362     public void setEncryption(boolean strength, String userPassword, String ownerPassword, int permissions) throws DocumentException {
363         setEncryption(DocWriter.getISOBytes(userPassword), DocWriter.getISOBytes(ownerPassword), permissions, strength);
364     }
365     */
366 
367     /** Gets a page from other PDF document. Note that calling this method more than
368      * once with the same parameters will retrieve the same object.
369      * @param reader the PDF document where the page is
370      * @param pageNumber the page number. The first page is 1
371      * @return the template representing the imported page
372      */
getImportedPage(PdfReader reader, int pageNumber)373     public PdfImportedPage getImportedPage(PdfReader reader, int pageNumber) throws IOException {
374         return stamper.getImportedPage(reader, pageNumber);
375     }
376 
377     /** Gets the underlying PdfWriter.
378      * @return the underlying PdfWriter
379      */
getWriter()380     public PdfWriter getWriter() {
381         return stamper;
382     }
383 
384     /** Gets the underlying PdfReader.
385      * @return the underlying PdfReader
386      */
getReader()387     public PdfReader getReader() {
388         return stamper.reader;
389     }
390 
391     /** Gets the <CODE>AcroFields</CODE> object that allows to get and set field values
392      * and to merge FDF forms.
393      * @return the <CODE>AcroFields</CODE> object
394      */
getAcroFields()395     public AcroFields getAcroFields() {
396         return stamper.getAcroFields();
397     }
398 
399     /** Determines if the fields are flattened on close. The fields added with
400      * {@link #addAnnotation(PdfAnnotation,int)} will never be flattened.
401      * @param flat <CODE>true</CODE> to flatten the fields, <CODE>false</CODE>
402      * to keep the fields
403      */
setFormFlattening(boolean flat)404     public void setFormFlattening(boolean flat) {
405         stamper.setFormFlattening(flat);
406     }
407 
408     /** Determines if the FreeText annotations are flattened on close.
409      * @param flat <CODE>true</CODE> to flatten the FreeText annotations, <CODE>false</CODE>
410      * (the default) to keep the FreeText annotations as active content.
411      */
setFreeTextFlattening(boolean flat)412     public void setFreeTextFlattening(boolean flat) {
413     	stamper.setFreeTextFlattening(flat);
414 	}
415 
416     /**
417      * Adds an annotation of form field in a specific page. This page number
418      * can be overridden with {@link PdfAnnotation#setPlaceInPage(int)}.
419      * @param annot the annotation
420      * @param page the page
421      */
addAnnotation(PdfAnnotation annot, int page)422     public void addAnnotation(PdfAnnotation annot, int page) {
423         stamper.addAnnotation(annot, page);
424     }
425 
426     /**
427      * Adds the comments present in an FDF file.
428      * @param fdf the FDF file
429      * @throws IOException on error
430      */
addComments(FdfReader fdf)431     public void addComments(FdfReader fdf) throws IOException {
432         stamper.addComments(fdf);
433     }
434 
435     /**
436      * Sets the bookmarks. The list structure is defined in
437      * {@link SimpleBookmark}.
438      * @param outlines the bookmarks or <CODE>null</CODE> to remove any
439      * @throws IOException on error
440      */
setOutlines(List outlines)441     public void setOutlines(List outlines) throws IOException {
442         stamper.setOutlines(outlines);
443     }
444 
445     /**
446      * Sets the thumbnail image for a page.
447      * @param image the image
448      * @param page the page
449      * @throws PdfException on error
450      * @throws DocumentException on error
451      */
452     /*
453     public void setThumbnail(Image image, int page) throws PdfException, DocumentException {
454         stamper.setThumbnail(image, page);
455     }
456     */
457 
458     /**
459      * Adds <CODE>name</CODE> to the list of fields that will be flattened on close,
460      * all the other fields will remain. If this method is never called or is called
461      * with invalid field names, all the fields will be flattened.
462      * <p>
463      * Calling <CODE>setFormFlattening(true)</CODE> is needed to have any kind of
464      * flattening.
465      * @param name the field name
466      * @return <CODE>true</CODE> if the field exists, <CODE>false</CODE> otherwise
467      */
partialFormFlattening(String name)468     public boolean partialFormFlattening(String name) {
469         return stamper.partialFormFlattening(name);
470     }
471 
472     /** Adds a JavaScript action at the document level. When the document
473      * opens all this JavaScript runs.
474      * @param js the JavaScript code
475      */
addJavaScript(String js)476     public void addJavaScript(String js) {
477         stamper.addJavaScript(js, !PdfEncodings.isPdfDocEncoding(js));
478     }
479 
480     /**
481      * Sets the viewer preferences.
482      * @param preferences the viewer preferences
483      * @see PdfWriter#setViewerPreferences(int)
484      */
setViewerPreferences(int preferences)485     public void setViewerPreferences(int preferences) {
486         stamper.setViewerPreferences(preferences);
487     }
488 
489     /**
490      * Sets the XMP metadata.
491      * @param xmp
492      * @see PdfWriter#setXmpMetadata(byte[])
493      */
setXmpMetadata(byte[] xmp)494     public void setXmpMetadata(byte[] xmp) {
495         stamper.setXmpMetadata(xmp);
496     }
497 
498     /**
499      * Gets the 1.5 compression status.
500      * @return <code>true</code> if the 1.5 compression is on
501      */
isFullCompression()502     public boolean isFullCompression() {
503         return stamper.isFullCompression();
504     }
505 
506     /**
507      * Sets the document's compression to the new 1.5 mode with object streams and xref
508      * streams. It can be set at any time but once set it can't be unset.
509      */
setFullCompression()510     public void setFullCompression() {
511         if (stamper.isAppend())
512             return;
513         stamper.setFullCompression();
514     }
515 
516     /**
517      * Sets the open and close page additional action.
518      * @param actionType the action type. It can be <CODE>PdfWriter.PAGE_OPEN</CODE>
519      * or <CODE>PdfWriter.PAGE_CLOSE</CODE>
520      * @param action the action to perform
521      * @param page the page where the action will be applied. The first page is 1
522      * @throws PdfException if the action type is invalid
523      */
setPageAction(PdfName actionType, PdfAction action, int page)524     public void setPageAction(PdfName actionType, PdfAction action, int page) throws PdfException {
525         stamper.setPageAction(actionType, action, page);
526     }
527 
528     /**
529      * Sets the display duration for the page (for presentations)
530      * @param seconds   the number of seconds to display the page. A negative value removes the entry
531      * @param page the page where the duration will be applied. The first page is 1
532      */
setDuration(int seconds, int page)533     public void setDuration(int seconds, int page) {
534         stamper.setDuration(seconds, page);
535     }
536 
537     /**
538      * Sets the transition for the page
539      * @param transition   the transition object. A <code>null</code> removes the transition
540      * @param page the page where the transition will be applied. The first page is 1
541      */
setTransition(PdfTransition transition, int page)542     public void setTransition(PdfTransition transition, int page) {
543         stamper.setTransition(transition, page);
544     }
545 
546     /**
547      * Applies a digital signature to a document, possibly as a new revision, making
548      * possible multiple signatures. The returned PdfStamper
549      * can be used normally as the signature is only applied when closing.
550      * <p>
551      * A possible use for adding a signature without invalidating an existing one is:
552      * <p>
553      * <pre>
554      * KeyStore ks = KeyStore.getInstance("pkcs12");
555      * ks.load(new FileInputStream("my_private_key.pfx"), "my_password".toCharArray());
556      * String alias = (String)ks.aliases().nextElement();
557      * PrivateKey key = (PrivateKey)ks.getKey(alias, "my_password".toCharArray());
558      * Certificate[] chain = ks.getCertificateChain(alias);
559      * PdfReader reader = new PdfReader("original.pdf");
560      * FileOutputStream fout = new FileOutputStream("signed.pdf");
561      * PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', new
562      * File("/temp"), true);
563      * PdfSignatureAppearance sap = stp.getSignatureAppearance();
564      * sap.setCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
565      * sap.setReason("I'm the author");
566      * sap.setLocation("Lisbon");
567      * // comment next line to have an invisible signature
568      * sap.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, null);
569      * stp.close();
570      * </pre>
571      * @param reader the original document
572      * @param os the output stream or <CODE>null</CODE> to keep the document in the temporary file
573      * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
574      * document
575      * @param tempFile location of the temporary file. If it's a directory a temporary file will be created there.
576      *     If it's a file it will be used directly. The file will be deleted on exit unless <CODE>os</CODE> is null.
577      *     In that case the document can be retrieved directly from the temporary file. If it's <CODE>null</CODE>
578      *     no temporary file will be created and memory will be used
579      * @param append if <CODE>true</CODE> the signature and all the other content will be added as a
580      * new revision thus not invalidating existing signatures
581      * @return a <CODE>PdfStamper</CODE>
582      * @throws DocumentException on error
583      * @throws IOException on error
584      */
createSignature(PdfReader reader, OutputStream os, char pdfVersion, File tempFile, boolean append)585     public static PdfStamper createSignature(PdfReader reader, OutputStream os, char pdfVersion, File tempFile, boolean append) throws DocumentException, IOException {
586         PdfStamper stp;
587         if (tempFile == null) {
588             ByteBuffer bout = new ByteBuffer();
589             stp = new PdfStamper(reader, bout, pdfVersion, append);
590             stp.sigApp = new PdfSignatureAppearance(stp.stamper);
591             stp.sigApp.setSigout(bout);
592         }
593         else {
594             if (tempFile.isDirectory())
595                 tempFile = File.createTempFile("pdf", null, tempFile);
596             FileOutputStream fout = new FileOutputStream(tempFile);
597             stp = new PdfStamper(reader, fout, pdfVersion, append);
598             stp.sigApp = new PdfSignatureAppearance(stp.stamper);
599             stp.sigApp.setTempFile(tempFile);
600         }
601         stp.sigApp.setOriginalout(os);
602         stp.sigApp.setStamper(stp);
603         stp.hasSignature = true;
604         return stp;
605     }
606 
607     /**
608      * Applies a digital signature to a document. The returned PdfStamper
609      * can be used normally as the signature is only applied when closing.
610      * <p>
611      * Note that the pdf is created in memory.
612      * <p>
613      * A possible use is:
614      * <p>
615      * <pre>
616      * KeyStore ks = KeyStore.getInstance("pkcs12");
617      * ks.load(new FileInputStream("my_private_key.pfx"), "my_password".toCharArray());
618      * String alias = (String)ks.aliases().nextElement();
619      * PrivateKey key = (PrivateKey)ks.getKey(alias, "my_password".toCharArray());
620      * Certificate[] chain = ks.getCertificateChain(alias);
621      * PdfReader reader = new PdfReader("original.pdf");
622      * FileOutputStream fout = new FileOutputStream("signed.pdf");
623      * PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0');
624      * PdfSignatureAppearance sap = stp.getSignatureAppearance();
625      * sap.setCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
626      * sap.setReason("I'm the author");
627      * sap.setLocation("Lisbon");
628      * // comment next line to have an invisible signature
629      * sap.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, null);
630      * stp.close();
631      * </pre>
632      * @param reader the original document
633      * @param os the output stream
634      * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
635      * document
636      * @throws DocumentException on error
637      * @throws IOException on error
638      * @return a <CODE>PdfStamper</CODE>
639      */
createSignature(PdfReader reader, OutputStream os, char pdfVersion)640     public static PdfStamper createSignature(PdfReader reader, OutputStream os, char pdfVersion) throws DocumentException, IOException {
641         return createSignature(reader, os, pdfVersion, null, false);
642     }
643 
644     /**
645      * Applies a digital signature to a document. The returned PdfStamper
646      * can be used normally as the signature is only applied when closing.
647      * <p>
648      * A possible use is:
649      * <p>
650      * <pre>
651      * KeyStore ks = KeyStore.getInstance("pkcs12");
652      * ks.load(new FileInputStream("my_private_key.pfx"), "my_password".toCharArray());
653      * String alias = (String)ks.aliases().nextElement();
654      * PrivateKey key = (PrivateKey)ks.getKey(alias, "my_password".toCharArray());
655      * Certificate[] chain = ks.getCertificateChain(alias);
656      * PdfReader reader = new PdfReader("original.pdf");
657      * FileOutputStream fout = new FileOutputStream("signed.pdf");
658      * PdfStamper stp = PdfStamper.createSignature(reader, fout, '\0', new File("/temp"));
659      * PdfSignatureAppearance sap = stp.getSignatureAppearance();
660      * sap.setCrypto(key, chain, null, PdfSignatureAppearance.WINCER_SIGNED);
661      * sap.setReason("I'm the author");
662      * sap.setLocation("Lisbon");
663      * // comment next line to have an invisible signature
664      * sap.setVisibleSignature(new Rectangle(100, 100, 200, 200), 1, null);
665      * stp.close();
666      * </pre>
667      * @param reader the original document
668      * @param os the output stream or <CODE>null</CODE> to keep the document in the temporary file
669      * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
670      * document
671      * @param tempFile location of the temporary file. If it's a directory a temporary file will be created there.
672      *     If it's a file it will be used directly. The file will be deleted on exit unless <CODE>os</CODE> is null.
673      *     In that case the document can be retrieved directly from the temporary file. If it's <CODE>null</CODE>
674      *     no temporary file will be created and memory will be used
675      * @return a <CODE>PdfStamper</CODE>
676      * @throws DocumentException on error
677      * @throws IOException on error
678      */
createSignature(PdfReader reader, OutputStream os, char pdfVersion, File tempFile)679     public static PdfStamper createSignature(PdfReader reader, OutputStream os, char pdfVersion, File tempFile) throws DocumentException, IOException
680     {
681         return createSignature(reader, os, pdfVersion, tempFile, false);
682     }
683 }