001/*
002 * $Id: PdfCopy.java 4784 2011-03-15 08:33:00Z blowagie $
003 *
004 * This file is part of the iText (R) project.
005 * Copyright (c) 1998-2011 1T3XT BVBA
006 * Authors: Bruno Lowagie, Paulo Soares, et al.
007 *
008 * This program is free software; you can redistribute it and/or modify
009 * it under the terms of the GNU Affero General Public License version 3
010 * as published by the Free Software Foundation with the addition of the
011 * following permission added to Section 15 as permitted in Section 7(a):
012 * FOR ANY PART OF THE COVERED WORK IN WHICH THE COPYRIGHT IS OWNED BY 1T3XT,
013 * 1T3XT DISCLAIMS THE WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
014 *
015 * This program is distributed in the hope that it will be useful, but
016 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
017 * or FITNESS FOR A PARTICULAR PURPOSE.
018 * See the GNU Affero General Public License for more details.
019 * You should have received a copy of the GNU Affero General Public License
020 * along with this program; if not, see http://www.gnu.org/licenses or write to
021 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
022 * Boston, MA, 02110-1301 USA, or download the license from the following URL:
023 * http://itextpdf.com/terms-of-use/
024 *
025 * The interactive user interfaces in modified source and object code versions
026 * of this program must display Appropriate Legal Notices, as required under
027 * Section 5 of the GNU Affero General Public License.
028 *
029 * In accordance with Section 7(b) of the GNU Affero General Public License,
030 * a covered work must retain the producer line in every PDF that is created
031 * or manipulated using iText.
032 *
033 * You can be released from the requirements of the license by purchasing
034 * a commercial license. Buying such a license is mandatory as soon as you
035 * develop commercial activities involving the iText software without
036 * disclosing the source code of your own applications.
037 * These activities include: offering paid services to customers as an ASP,
038 * serving PDFs on the fly in a web application, shipping iText with a closed
039 * source product.
040 *
041 * For more information, please contact iText Software Corp. at this
042 * address: sales@itextpdf.com
043 */
044package com.itextpdf.text.pdf;
045
046import java.io.IOException;
047import java.io.OutputStream;
048import java.util.ArrayList;
049import java.util.HashMap;
050import java.util.HashSet;
051import java.util.Iterator;
052
053import com.itextpdf.text.Document;
054import com.itextpdf.text.DocumentException;
055import com.itextpdf.text.ExceptionConverter;
056import com.itextpdf.text.Rectangle;
057
058/**
059 * Make copies of PDF documents. Documents can be edited after reading and
060 * before writing them out.
061 * @author Mark Thompson
062 */
063
064public class PdfCopy extends PdfWriter {
065    /**
066     * This class holds information about indirect references, since they are
067     * renumbered by iText.
068     */
069    static class IndirectReferences {
070        PdfIndirectReference theRef;
071        boolean hasCopied;
072        IndirectReferences(PdfIndirectReference ref) {
073            theRef = ref;
074            hasCopied = false;
075        }
076        void setCopied() { hasCopied = true; }
077        boolean getCopied() { return hasCopied; }
078        PdfIndirectReference getRef() { return theRef; }
079    };
080    protected HashMap<RefKey, IndirectReferences> indirects;
081    protected HashMap<PdfReader, HashMap<RefKey, IndirectReferences>> indirectMap;
082    protected int currentObjectNum = 1;
083    protected PdfReader reader;
084    protected PdfIndirectReference acroForm;
085    protected int[] namePtr = {0};
086    /** Holds value of property rotateContents. */
087    private boolean rotateContents = true;
088    protected PdfArray fieldArray;
089    protected HashSet<PdfTemplate> fieldTemplates;
090
091    /**
092     * A key to allow us to hash indirect references
093     */
094    protected static class RefKey {
095        int num;
096        int gen;
097        RefKey(int num, int gen) {
098            this.num = num;
099            this.gen = gen;
100        }
101        RefKey(PdfIndirectReference ref) {
102            num = ref.getNumber();
103            gen = ref.getGeneration();
104        }
105        RefKey(PRIndirectReference ref) {
106            num = ref.getNumber();
107            gen = ref.getGeneration();
108        }
109        @Override
110        public int hashCode() {
111            return (gen<<16)+num;
112        }
113        @Override
114        public boolean equals(Object o) {
115            if (!(o instanceof RefKey)) return false;
116            RefKey other = (RefKey)o;
117            return this.gen == other.gen && this.num == other.num;
118        }
119        @Override
120        public String toString() {
121            return Integer.toString(num) + ' ' + gen;
122        }
123    }
124
125    /**
126     * Constructor
127     * @param document
128     * @param os outputstream
129     */
130    public PdfCopy(Document document, OutputStream os) throws DocumentException {
131        super(new PdfDocument(), os);
132        document.addDocListener(pdf);
133        pdf.addWriter(this);
134        indirectMap = new HashMap<PdfReader, HashMap<RefKey, IndirectReferences>>();
135    }
136
137    /** Getter for property rotateContents.
138     * @return Value of property rotateContents.
139     *
140     */
141    public boolean isRotateContents() {
142        return this.rotateContents;
143    }
144
145    /** Setter for property rotateContents.
146     * @param rotateContents New value of property rotateContents.
147     *
148     */
149    public void setRotateContents(boolean rotateContents) {
150        this.rotateContents = rotateContents;
151    }
152
153    /**
154     * Grabs a page from the input document
155     * @param reader the reader of the document
156     * @param pageNumber which page to get
157     * @return the page
158     */
159    @Override
160    public PdfImportedPage getImportedPage(PdfReader reader, int pageNumber) {
161        if (currentPdfReaderInstance != null) {
162            if (currentPdfReaderInstance.getReader() != reader) {
163                try {
164                    currentPdfReaderInstance.getReader().close();
165                    currentPdfReaderInstance.getReaderFile().close();
166                }
167                catch (IOException ioe) {
168                    // empty on purpose
169                }
170                currentPdfReaderInstance = super.getPdfReaderInstance(reader);
171            }
172        }
173        else {
174            currentPdfReaderInstance = super.getPdfReaderInstance(reader);
175        }
176        //currentPdfReaderInstance.setOutputToPdf(false);
177        return currentPdfReaderInstance.getImportedPage(pageNumber);
178    }
179
180
181    /**
182     * Translate a PRIndirectReference to a PdfIndirectReference
183     * In addition, translates the object numbers, and copies the
184     * referenced object to the output file.
185     * NB: PRIndirectReferences (and PRIndirectObjects) really need to know what
186     * file they came from, because each file has its own namespace. The translation
187     * we do from their namespace to ours is *at best* heuristic, and guaranteed to
188     * fail under some circumstances.
189     */
190    protected PdfIndirectReference copyIndirect(PRIndirectReference in) throws IOException, BadPdfFormatException {
191        PdfIndirectReference theRef;
192        RefKey key = new RefKey(in);
193        IndirectReferences iRef = indirects.get(key);
194        if (iRef != null) {
195            theRef = iRef.getRef();
196            if (iRef.getCopied()) {
197                return theRef;
198            }
199        }
200        else {
201            theRef = body.getPdfIndirectReference();
202            iRef = new IndirectReferences(theRef);
203            indirects.put(key, iRef);
204        }
205        PdfObject obj = PdfReader.getPdfObjectRelease(in);
206        if (obj != null && obj.isDictionary()) {
207            PdfObject type = PdfReader.getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.TYPE));
208            if (type != null && PdfName.PAGE.equals(type)) {
209                return theRef;
210            }
211        }
212        iRef.setCopied();
213        obj = copyObject(obj);
214        addToBody(obj, theRef);
215        return theRef;
216    }
217
218    /**
219     * Translate a PRDictionary to a PdfDictionary. Also translate all of the
220     * objects contained in it.
221     */
222    protected PdfDictionary copyDictionary(PdfDictionary in)
223    throws IOException, BadPdfFormatException {
224        PdfDictionary out = new PdfDictionary();
225        PdfObject type = PdfReader.getPdfObjectRelease(in.get(PdfName.TYPE));
226
227        for (Object element : in.getKeys()) {
228            PdfName key = (PdfName)element;
229            PdfObject value = in.get(key);
230            //      System.out.println("Copy " + key);
231            if (type != null && PdfName.PAGE.equals(type)) {
232                if (!key.equals(PdfName.B) && !key.equals(PdfName.PARENT))
233                    out.put(key, copyObject(value));
234            }
235            else
236                out.put(key, copyObject(value));
237        }
238        return out;
239    }
240
241    /**
242     * Translate a PRStream to a PdfStream. The data part copies itself.
243     */
244    protected PdfStream copyStream(PRStream in) throws IOException, BadPdfFormatException {
245        PRStream out = new PRStream(in, null);
246
247        for (Object element : in.getKeys()) {
248            PdfName key = (PdfName) element;
249            PdfObject value = in.get(key);
250            out.put(key, copyObject(value));
251        }
252
253        return out;
254    }
255
256
257    /**
258     * Translate a PRArray to a PdfArray. Also translate all of the objects contained
259     * in it
260     */
261    protected PdfArray copyArray(PdfArray in) throws IOException, BadPdfFormatException {
262        PdfArray out = new PdfArray();
263
264        for (Iterator<PdfObject> i = in.listIterator(); i.hasNext();) {
265            PdfObject value = i.next();
266            out.add(copyObject(value));
267        }
268        return out;
269    }
270
271    /**
272     * Translate a PR-object to a Pdf-object
273     */
274    protected PdfObject copyObject(PdfObject in) throws IOException,BadPdfFormatException {
275        if (in == null)
276            return PdfNull.PDFNULL;
277        switch (in.type) {
278            case PdfObject.DICTIONARY:
279                //              System.out.println("Dictionary: " + in.toString());
280                return copyDictionary((PdfDictionary)in);
281            case PdfObject.INDIRECT:
282                return copyIndirect((PRIndirectReference)in);
283            case PdfObject.ARRAY:
284                return copyArray((PdfArray)in);
285            case PdfObject.NUMBER:
286            case PdfObject.NAME:
287            case PdfObject.STRING:
288            case PdfObject.NULL:
289            case PdfObject.BOOLEAN:
290            case 0:
291                return in;
292            case PdfObject.STREAM:
293                return copyStream((PRStream)in);
294                //                return in;
295            default:
296                if (in.type < 0) {
297                    String lit = ((PdfLiteral)in).toString();
298                    if (lit.equals("true") || lit.equals("false")) {
299                        return new PdfBoolean(lit);
300                    }
301                    return new PdfLiteral(lit);
302                }
303                System.out.println("CANNOT COPY type " + in.type);
304                return null;
305        }
306    }
307
308    /**
309     * convenience method. Given an imported page, set our "globals"
310     */
311    protected int setFromIPage(PdfImportedPage iPage) {
312        int pageNum = iPage.getPageNumber();
313        PdfReaderInstance inst = currentPdfReaderInstance = iPage.getPdfReaderInstance();
314        reader = inst.getReader();
315        setFromReader(reader);
316        return pageNum;
317    }
318
319    /**
320     * convenience method. Given a reader, set our "globals"
321     */
322    protected void setFromReader(PdfReader reader) {
323        this.reader = reader;
324        indirects = indirectMap.get(reader);
325        if (indirects == null) {
326            indirects = new HashMap<RefKey, IndirectReferences>();
327            indirectMap.put(reader,indirects);
328            PdfDictionary catalog = reader.getCatalog();
329            PRIndirectReference ref = null;
330            PdfObject o = catalog.get(PdfName.ACROFORM);
331            if (o == null || o.type() != PdfObject.INDIRECT)
332                return;
333            ref = (PRIndirectReference)o;
334            if (acroForm == null) acroForm = body.getPdfIndirectReference();
335            indirects.put(new RefKey(ref), new IndirectReferences(acroForm));
336        }
337    }
338    /**
339     * Add an imported page to our output
340     * @param iPage an imported page
341     * @throws IOException, BadPdfFormatException
342     */
343    public void addPage(PdfImportedPage iPage) throws IOException, BadPdfFormatException {
344        int pageNum = setFromIPage(iPage);
345
346        PdfDictionary thePage = reader.getPageN(pageNum);
347        PRIndirectReference origRef = reader.getPageOrigRef(pageNum);
348        reader.releasePage(pageNum);
349        RefKey key = new RefKey(origRef);
350        PdfIndirectReference pageRef;
351        IndirectReferences iRef = indirects.get(key);
352        if (iRef != null && !iRef.getCopied()) {
353            pageReferences.add(iRef.getRef());
354            iRef.setCopied();
355        }
356        pageRef = getCurrentPage();
357        if (iRef == null) {
358            iRef = new IndirectReferences(pageRef);
359            indirects.put(key, iRef);
360        }
361        iRef.setCopied();
362        PdfDictionary newPage = copyDictionary(thePage);
363        root.addPage(newPage);
364        iPage.setCopied();
365        ++currentPageNumber;
366    }
367
368    /**
369     * Adds a blank page.
370     * @param   rect The page dimension
371     * @param   rotation The rotation angle in degrees
372     * @since   2.1.5
373     */
374    public void addPage(Rectangle rect, int rotation) {
375        PdfRectangle mediabox = new PdfRectangle(rect, rotation);
376        PageResources resources = new PageResources();
377        PdfPage page = new PdfPage(mediabox, new HashMap<String, PdfRectangle>(), resources.getResources(), 0);
378        page.put(PdfName.TABS, getTabs());
379        root.addPage(page);
380        ++currentPageNumber;
381    }
382
383    /**
384     * Copy the acroform for an input document. Note that you can only have one,
385     * we make no effort to merge them.
386     * @param reader The reader of the input file that is being copied
387     * @throws IOException, BadPdfFormatException
388     */
389    public void copyAcroForm(PdfReader reader) throws IOException, BadPdfFormatException {
390        setFromReader(reader);
391
392        PdfDictionary catalog = reader.getCatalog();
393        PRIndirectReference hisRef = null;
394        PdfObject o = catalog.get(PdfName.ACROFORM);
395        if (o != null && o.type() == PdfObject.INDIRECT)
396            hisRef = (PRIndirectReference)o;
397        if (hisRef == null) return; // bugfix by John Englar
398        RefKey key = new RefKey(hisRef);
399        PdfIndirectReference myRef;
400        IndirectReferences iRef = indirects.get(key);
401        if (iRef != null) {
402            acroForm = myRef = iRef.getRef();
403        }
404        else {
405            acroForm = myRef = body.getPdfIndirectReference();
406            iRef = new IndirectReferences(myRef);
407            indirects.put(key, iRef);
408        }
409        if (! iRef.getCopied()) {
410            iRef.setCopied();
411            PdfDictionary theForm = copyDictionary((PdfDictionary)PdfReader.getPdfObject(hisRef));
412            addToBody(theForm, myRef);
413        }
414    }
415
416    /*
417     * the getCatalog method is part of PdfWriter.
418     * we wrap this so that we can extend it
419     */
420    @Override
421    protected PdfDictionary getCatalog(PdfIndirectReference rootObj) {
422        try {
423            PdfDictionary theCat = pdf.getCatalog(rootObj);
424            if (fieldArray == null) {
425                if (acroForm != null) theCat.put(PdfName.ACROFORM, acroForm);
426            }
427            else
428                addFieldResources(theCat);
429            return theCat;
430        }
431        catch (IOException e) {
432            throw new ExceptionConverter(e);
433        }
434    }
435
436    private void addFieldResources(PdfDictionary catalog) throws IOException {
437        if (fieldArray == null)
438            return;
439        PdfDictionary acroForm = new PdfDictionary();
440        catalog.put(PdfName.ACROFORM, acroForm);
441        acroForm.put(PdfName.FIELDS, fieldArray);
442        acroForm.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
443        if (fieldTemplates.isEmpty())
444            return;
445        PdfDictionary dr = new PdfDictionary();
446        acroForm.put(PdfName.DR, dr);
447        for (PdfTemplate template: fieldTemplates) {
448            PdfFormField.mergeResources(dr, (PdfDictionary)template.getResources());
449        }
450        // if (dr.get(PdfName.ENCODING) == null) dr.put(PdfName.ENCODING, PdfName.WIN_ANSI_ENCODING);
451        PdfDictionary fonts = dr.getAsDict(PdfName.FONT);
452        if (fonts == null) {
453            fonts = new PdfDictionary();
454            dr.put(PdfName.FONT, fonts);
455        }
456        if (!fonts.contains(PdfName.HELV)) {
457            PdfDictionary dic = new PdfDictionary(PdfName.FONT);
458            dic.put(PdfName.BASEFONT, PdfName.HELVETICA);
459            dic.put(PdfName.ENCODING, PdfName.WIN_ANSI_ENCODING);
460            dic.put(PdfName.NAME, PdfName.HELV);
461            dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
462            fonts.put(PdfName.HELV, addToBody(dic).getIndirectReference());
463        }
464        if (!fonts.contains(PdfName.ZADB)) {
465            PdfDictionary dic = new PdfDictionary(PdfName.FONT);
466            dic.put(PdfName.BASEFONT, PdfName.ZAPFDINGBATS);
467            dic.put(PdfName.NAME, PdfName.ZADB);
468            dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
469            fonts.put(PdfName.ZADB, addToBody(dic).getIndirectReference());
470        }
471    }
472
473    /**
474     * Signals that the <CODE>Document</CODE> was closed and that no other
475     * <CODE>Elements</CODE> will be added.
476     * <P>
477     * The pages-tree is built and written to the outputstream.
478     * A Catalog is constructed, as well as an Info-object,
479     * the reference table is composed and everything is written
480     * to the outputstream embedded in a Trailer.
481     */
482
483    @Override
484    public void close() {
485        if (open) {
486            PdfReaderInstance ri = currentPdfReaderInstance;
487            pdf.close();
488            super.close();
489            if (ri != null) {
490                try {
491                    ri.getReader().close();
492                    ri.getReaderFile().close();
493                }
494                catch (IOException ioe) {
495                    // empty on purpose
496                }
497            }
498        }
499    }
500    public PdfIndirectReference add(PdfOutline outline) { return null; }
501    @Override
502    public void addAnnotation(PdfAnnotation annot) {  }
503    @Override
504    PdfIndirectReference add(PdfPage page, PdfContents contents) throws PdfException { return null; }
505
506    @Override
507    public void freeReader(PdfReader reader) throws IOException {
508        indirectMap.remove(reader);
509        if (currentPdfReaderInstance != null) {
510            if (currentPdfReaderInstance.getReader() == reader) {
511                try {
512                    currentPdfReaderInstance.getReader().close();
513                    currentPdfReaderInstance.getReaderFile().close();
514                }
515                catch (IOException ioe) {
516                    // empty on purpose
517                }
518                currentPdfReaderInstance = null;
519            }
520        }
521        super.freeReader(reader);
522    }
523
524    /**
525     * Create a page stamp. New content and annotations, including new fields, are allowed.
526     * The fields added cannot have parents in another pages. This method modifies the PdfReader instance.<p>
527     * The general usage to stamp something in a page is:
528     * <p>
529     * <pre>
530     * PdfImportedPage page = copy.getImportedPage(reader, 1);
531     * PdfCopy.PageStamp ps = copy.createPageStamp(page);
532     * ps.addAnnotation(PdfAnnotation.createText(copy, new Rectangle(50, 180, 70, 200), "Hello", "No Thanks", true, "Comment"));
533     * PdfContentByte under = ps.getUnderContent();
534     * under.addImage(img);
535     * PdfContentByte over = ps.getOverContent();
536     * over.beginText();
537     * over.setFontAndSize(bf, 18);
538     * over.setTextMatrix(30, 30);
539     * over.showText("total page " + totalPage);
540     * over.endText();
541     * ps.alterContents();
542     * copy.addPage(page);
543     * </pre>
544     * @param iPage an imported page
545     * @return the <CODE>PageStamp</CODE>
546     */
547    public PageStamp createPageStamp(PdfImportedPage iPage) {
548        int pageNum = iPage.getPageNumber();
549        PdfReader reader = iPage.getPdfReaderInstance().getReader();
550        PdfDictionary pageN = reader.getPageN(pageNum);
551        return new PageStamp(reader, pageN, this);
552    }
553
554    public static class PageStamp {
555
556        PdfDictionary pageN;
557        PdfCopy.StampContent under;
558        PdfCopy.StampContent over;
559        PageResources pageResources;
560        PdfReader reader;
561        PdfCopy cstp;
562
563        PageStamp(PdfReader reader, PdfDictionary pageN, PdfCopy cstp) {
564            this.pageN = pageN;
565            this.reader = reader;
566            this.cstp = cstp;
567        }
568
569        public PdfContentByte getUnderContent(){
570            if (under == null) {
571                if (pageResources == null) {
572                    pageResources = new PageResources();
573                    PdfDictionary resources = pageN.getAsDict(PdfName.RESOURCES);
574                    pageResources.setOriginalResources(resources, cstp.namePtr);
575                }
576                under = new PdfCopy.StampContent(cstp, pageResources);
577            }
578            return under;
579        }
580
581        public PdfContentByte getOverContent(){
582            if (over == null) {
583                if (pageResources == null) {
584                    pageResources = new PageResources();
585                    PdfDictionary resources = pageN.getAsDict(PdfName.RESOURCES);
586                    pageResources.setOriginalResources(resources, cstp.namePtr);
587                }
588                over = new PdfCopy.StampContent(cstp, pageResources);
589            }
590            return over;
591        }
592
593        public void alterContents() throws IOException {
594            if (over == null && under == null)
595                return;
596            PdfArray ar = null;
597            PdfObject content = PdfReader.getPdfObject(pageN.get(PdfName.CONTENTS), pageN);
598            if (content == null) {
599                ar = new PdfArray();
600                pageN.put(PdfName.CONTENTS, ar);
601            } else if (content.isArray()) {
602                ar = (PdfArray)content;
603            } else if (content.isStream()) {
604                ar = new PdfArray();
605                ar.add(pageN.get(PdfName.CONTENTS));
606                pageN.put(PdfName.CONTENTS, ar);
607            } else {
608                ar = new PdfArray();
609                pageN.put(PdfName.CONTENTS, ar);
610            }
611            ByteBuffer out = new ByteBuffer();
612            if (under != null) {
613                out.append(PdfContents.SAVESTATE);
614                applyRotation(pageN, out);
615                out.append(under.getInternalBuffer());
616                out.append(PdfContents.RESTORESTATE);
617            }
618            if (over != null)
619                out.append(PdfContents.SAVESTATE);
620            PdfStream stream = new PdfStream(out.toByteArray());
621            stream.flateCompress(cstp.getCompressionLevel());
622            PdfIndirectReference ref1 = cstp.addToBody(stream).getIndirectReference();
623            ar.addFirst(ref1);
624            out.reset();
625            if (over != null) {
626                out.append(' ');
627                out.append(PdfContents.RESTORESTATE);
628                out.append(PdfContents.SAVESTATE);
629                applyRotation(pageN, out);
630                out.append(over.getInternalBuffer());
631                out.append(PdfContents.RESTORESTATE);
632                stream = new PdfStream(out.toByteArray());
633                stream.flateCompress(cstp.getCompressionLevel());
634                ar.add(cstp.addToBody(stream).getIndirectReference());
635            }
636            pageN.put(PdfName.RESOURCES, pageResources.getResources());
637        }
638
639        void applyRotation(PdfDictionary pageN, ByteBuffer out) {
640            if (!cstp.rotateContents)
641                return;
642            Rectangle page = reader.getPageSizeWithRotation(pageN);
643            int rotation = page.getRotation();
644            switch (rotation) {
645                case 90:
646                    out.append(PdfContents.ROTATE90);
647                    out.append(page.getTop());
648                    out.append(' ').append('0').append(PdfContents.ROTATEFINAL);
649                    break;
650                case 180:
651                    out.append(PdfContents.ROTATE180);
652                    out.append(page.getRight());
653                    out.append(' ');
654                    out.append(page.getTop());
655                    out.append(PdfContents.ROTATEFINAL);
656                    break;
657                case 270:
658                    out.append(PdfContents.ROTATE270);
659                    out.append('0').append(' ');
660                    out.append(page.getRight());
661                    out.append(PdfContents.ROTATEFINAL);
662                    break;
663            }
664        }
665
666        private void addDocumentField(PdfIndirectReference ref) {
667            if (cstp.fieldArray == null)
668                cstp.fieldArray = new PdfArray();
669            cstp.fieldArray.add(ref);
670        }
671
672        private void expandFields(PdfFormField field, ArrayList<PdfAnnotation> allAnnots) {
673            allAnnots.add(field);
674            ArrayList<PdfFormField> kids = field.getKids();
675            if (kids != null) {
676                for (PdfFormField f : kids)
677                    expandFields(f, allAnnots);
678            }
679        }
680
681        public void addAnnotation(PdfAnnotation annot) {
682            try {
683                ArrayList<PdfAnnotation> allAnnots = new ArrayList<PdfAnnotation>();
684                if (annot.isForm()) {
685                    PdfFormField field = (PdfFormField)annot;
686                    if (field.getParent() != null)
687                        return;
688                    expandFields(field, allAnnots);
689                    if (cstp.fieldTemplates == null)
690                        cstp.fieldTemplates = new HashSet<PdfTemplate>();
691                }
692                else
693                    allAnnots.add(annot);
694                for (int k = 0; k < allAnnots.size(); ++k) {
695                    annot = allAnnots.get(k);
696                    if (annot.isForm()) {
697                        if (!annot.isUsed()) {
698                            HashSet<PdfTemplate> templates = annot.getTemplates();
699                            if (templates != null)
700                                cstp.fieldTemplates.addAll(templates);
701                        }
702                        PdfFormField field = (PdfFormField)annot;
703                        if (field.getParent() == null)
704                            addDocumentField(field.getIndirectReference());
705                    }
706                    if (annot.isAnnotation()) {
707                        PdfObject pdfobj = PdfReader.getPdfObject(pageN.get(PdfName.ANNOTS), pageN);
708                        PdfArray annots = null;
709                        if (pdfobj == null || !pdfobj.isArray()) {
710                            annots = new PdfArray();
711                            pageN.put(PdfName.ANNOTS, annots);
712                        }
713                        else
714                            annots = (PdfArray)pdfobj;
715                        annots.add(annot.getIndirectReference());
716                        if (!annot.isUsed()) {
717                            PdfRectangle rect = (PdfRectangle)annot.get(PdfName.RECT);
718                            if (rect != null && (rect.left() != 0 || rect.right() != 0 || rect.top() != 0 || rect.bottom() != 0)) {
719                                int rotation = reader.getPageRotation(pageN);
720                                Rectangle pageSize = reader.getPageSizeWithRotation(pageN);
721                                switch (rotation) {
722                                    case 90:
723                                        annot.put(PdfName.RECT, new PdfRectangle(
724                                        pageSize.getTop() - rect.bottom(),
725                                        rect.left(),
726                                        pageSize.getTop() - rect.top(),
727                                        rect.right()));
728                                        break;
729                                    case 180:
730                                        annot.put(PdfName.RECT, new PdfRectangle(
731                                        pageSize.getRight() - rect.left(),
732                                        pageSize.getTop() - rect.bottom(),
733                                        pageSize.getRight() - rect.right(),
734                                        pageSize.getTop() - rect.top()));
735                                        break;
736                                    case 270:
737                                        annot.put(PdfName.RECT, new PdfRectangle(
738                                        rect.bottom(),
739                                        pageSize.getRight() - rect.left(),
740                                        rect.top(),
741                                        pageSize.getRight() - rect.right()));
742                                        break;
743                                }
744                            }
745                        }
746                    }
747                    if (!annot.isUsed()) {
748                        annot.setUsed();
749                        cstp.addToBody(annot, annot.getIndirectReference());
750                    }
751                }
752            }
753            catch (IOException e) {
754                throw new ExceptionConverter(e);
755            }
756        }
757    }
758
759    public static class StampContent extends PdfContentByte {
760        PageResources pageResources;
761
762        /** Creates a new instance of StampContent */
763        StampContent(PdfWriter writer, PageResources pageResources) {
764            super(writer);
765            this.pageResources = pageResources;
766        }
767
768        /**
769         * Gets a duplicate of this <CODE>PdfContentByte</CODE>. All
770         * the members are copied by reference but the buffer stays different.
771         *
772         * @return a copy of this <CODE>PdfContentByte</CODE>
773         */
774        @Override
775        public PdfContentByte getDuplicate() {
776            return new PdfCopy.StampContent(writer, pageResources);
777        }
778
779        @Override
780        PageResources getPageResources() {
781            return pageResources;
782        }
783    }
784}