001/*
002 * $Id: PdfStamperImp.java 4888 2011-05-29 14:57:01Z 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.ByteArrayOutputStream;
047import java.io.IOException;
048import java.io.OutputStream;
049import java.util.ArrayList;
050import java.util.HashMap;
051import java.util.HashSet;
052import java.util.Iterator;
053import java.util.Map;
054
055import org.xml.sax.SAXException;
056
057import com.itextpdf.text.Document;
058import com.itextpdf.text.DocumentException;
059import com.itextpdf.text.ExceptionConverter;
060import com.itextpdf.text.Image;
061import com.itextpdf.text.Rectangle;
062import com.itextpdf.text.error_messages.MessageLocalization;
063import com.itextpdf.text.exceptions.BadPasswordException;
064import com.itextpdf.text.pdf.AcroFields.Item;
065import com.itextpdf.text.pdf.collection.PdfCollection;
066import com.itextpdf.text.pdf.interfaces.PdfViewerPreferences;
067import com.itextpdf.text.pdf.internal.PdfViewerPreferencesImp;
068import com.itextpdf.text.xml.xmp.XmpReader;
069import com.itextpdf.text.xml.xmp.XmpWriter;
070
071class PdfStamperImp extends PdfWriter {
072    HashMap<PdfReader, IntHashtable> readers2intrefs = new HashMap<PdfReader, IntHashtable>();
073    HashMap<PdfReader, RandomAccessFileOrArray> readers2file = new HashMap<PdfReader, RandomAccessFileOrArray>();
074    RandomAccessFileOrArray file;
075    PdfReader reader;
076    IntHashtable myXref = new IntHashtable();
077    /** Integer(page number) -> PageStamp */
078    HashMap<PdfDictionary, PageStamp> pagesToContent = new HashMap<PdfDictionary, PageStamp>();
079    boolean closed = false;
080    /** Holds value of property rotateContents. */
081    private boolean rotateContents = true;
082    protected AcroFields acroFields;
083    protected boolean flat = false;
084    protected boolean flatFreeText = false;
085    protected int namePtr[] = {0};
086    protected HashSet<String> partialFlattening = new HashSet<String>();
087    protected boolean useVp = false;
088    protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
089    protected HashSet<PdfTemplate> fieldTemplates = new HashSet<PdfTemplate>();
090    protected boolean fieldsAdded = false;
091    protected int sigFlags = 0;
092    protected boolean append;
093    protected IntHashtable marked;
094    protected int initialXrefSize;
095    protected PdfAction openAction;
096
097    /** Creates new PdfStamperImp.
098     * @param reader the read PDF
099     * @param os the output destination
100     * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
101     * document
102     * @param append
103     * @throws DocumentException on error
104     * @throws IOException
105     */
106    PdfStamperImp(PdfReader reader, OutputStream os, char pdfVersion, boolean append) throws DocumentException, IOException {
107        super(new PdfDocument(), os);
108        if (!reader.isOpenedWithFullPermissions())
109            throw new BadPasswordException(MessageLocalization.getComposedMessage("pdfreader.not.opened.with.owner.password"));
110        if (reader.isTampered())
111            throw new DocumentException(MessageLocalization.getComposedMessage("the.original.document.was.reused.read.it.again.from.file"));
112        reader.setTampered(true);
113        this.reader = reader;
114        file = reader.getSafeFile();
115        this.append = append;
116        if (append) {
117            if (reader.isRebuilt())
118                throw new DocumentException(MessageLocalization.getComposedMessage("append.mode.requires.a.document.without.errors.even.if.recovery.was.possible"));
119            if (reader.isEncrypted())
120                crypto = new PdfEncryption(reader.getDecrypt());
121            pdf_version.setAppendmode(true);
122            file.reOpen();
123            byte buf[] = new byte[8192];
124            int n;
125            while ((n = file.read(buf)) > 0)
126                this.os.write(buf, 0, n);
127            file.close();
128            prevxref = reader.getLastXref();
129            reader.setAppendable(true);
130        }
131        else {
132            if (pdfVersion == 0)
133                super.setPdfVersion(reader.getPdfVersion());
134            else
135                super.setPdfVersion(pdfVersion);
136        }
137        super.open();
138        pdf.addWriter(this);
139        if (append) {
140            body.setRefnum(reader.getXrefSize());
141            marked = new IntHashtable();
142            if (reader.isNewXrefType())
143                fullCompression = true;
144            if (reader.isHybridXref())
145                fullCompression = false;
146        }
147        initialXrefSize = reader.getXrefSize();
148    }
149
150    void close(Map<String, String> moreInfo) throws IOException {
151        if (closed)
152            return;
153        if (useVp) {
154            reader.setViewerPreferences(viewerPreferences);
155            markUsed(reader.getTrailer().get(PdfName.ROOT));
156        }
157        if (flat)
158            flatFields();
159        if (flatFreeText)
160                flatFreeTextFields();
161        addFieldResources();
162        PdfDictionary catalog = reader.getCatalog();
163        PdfDictionary pages = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.PAGES));
164        pages.put(PdfName.ITXT, new PdfString(Document.getRelease()));
165        markUsed(pages);
166        PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), reader.getCatalog());
167        if (acroFields != null && acroFields.getXfa().isChanged()) {
168            markUsed(acroForm);
169            if (!flat)
170                acroFields.getXfa().setXfa(this);
171        }
172        if (sigFlags != 0) {
173            if (acroForm != null) {
174                acroForm.put(PdfName.SIGFLAGS, new PdfNumber(sigFlags));
175                markUsed(acroForm);
176                markUsed(catalog);
177            }
178        }
179        closed = true;
180        addSharedObjectsToBody();
181        setOutlines();
182        setJavaScript();
183        addFileAttachments();
184        if (openAction != null) {
185            catalog.put(PdfName.OPENACTION, openAction);
186        }
187        if (pdf.pageLabels != null)
188            catalog.put(PdfName.PAGELABELS, pdf.pageLabels.getDictionary(this));
189        // OCG
190        if (!documentOCG.isEmpty()) {
191                fillOCProperties(false);
192                PdfDictionary ocdict = catalog.getAsDict(PdfName.OCPROPERTIES);
193                if (ocdict == null) {
194                        reader.getCatalog().put(PdfName.OCPROPERTIES, OCProperties);
195                }
196                else {
197                        ocdict.put(PdfName.OCGS, OCProperties.get(PdfName.OCGS));
198                        PdfDictionary ddict = ocdict.getAsDict(PdfName.D);
199                        if (ddict == null) {
200                                ddict = new PdfDictionary();
201                                ocdict.put(PdfName.D, ddict);
202                        }
203                        ddict.put(PdfName.ORDER, OCProperties.getAsDict(PdfName.D).get(PdfName.ORDER));
204                        ddict.put(PdfName.RBGROUPS, OCProperties.getAsDict(PdfName.D).get(PdfName.RBGROUPS));
205                        ddict.put(PdfName.OFF, OCProperties.getAsDict(PdfName.D).get(PdfName.OFF));
206                        ddict.put(PdfName.AS, OCProperties.getAsDict(PdfName.D).get(PdfName.AS));
207            }
208        }
209        // metadata
210        int skipInfo = -1;
211        PdfObject oInfo = reader.getTrailer().get(PdfName.INFO);
212        PRIndirectReference iInfo = null;
213        PdfDictionary oldInfo = null;
214        if (oInfo instanceof PRIndirectReference)
215            iInfo = (PRIndirectReference)oInfo;
216        if (iInfo != null)
217            oldInfo = (PdfDictionary)PdfReader.getPdfObject(iInfo);
218        else if (oInfo instanceof PdfDictionary)
219            oldInfo = (PdfDictionary)oInfo;
220        String producer = null;
221        if (iInfo != null)
222            skipInfo = iInfo.getNumber();
223        if (oldInfo != null && oldInfo.get(PdfName.PRODUCER) != null)
224                producer = oldInfo.getAsString(PdfName.PRODUCER).toUnicodeString();
225        if (producer == null) {
226                producer = Document.getVersion();
227        }
228        else if (producer.indexOf(Document.getProduct()) == -1) {
229                StringBuffer buf = new StringBuffer(producer);
230                buf.append("; modified using ");
231                buf.append(Document.getVersion());
232                producer = buf.toString();
233        }
234        PdfIndirectReference info = null;
235        PdfDictionary newInfo = new PdfDictionary();
236        if (oldInfo != null) {
237            for (Object element : oldInfo.getKeys()) {
238                PdfName key = (PdfName)element;
239                PdfObject value = PdfReader.getPdfObject(oldInfo.get(key));
240                newInfo.put(key, value);
241            }
242        }
243        if (moreInfo != null) {
244            for (Map.Entry<String, String> entry: moreInfo.entrySet()) {
245                String key = entry.getKey();
246                PdfName keyName = new PdfName(key);
247                String value = entry.getValue();
248                if (value == null)
249                    newInfo.remove(keyName);
250                else
251                    newInfo.put(keyName, new PdfString(value, PdfObject.TEXT_UNICODE));
252            }
253        }
254        PdfDate date = new PdfDate();
255        newInfo.put(PdfName.MODDATE, date);
256        newInfo.put(PdfName.PRODUCER, new PdfString(producer, PdfObject.TEXT_UNICODE));
257        if (append) {
258            if (iInfo == null)
259                info = addToBody(newInfo, false).getIndirectReference();
260            else
261                info = addToBody(newInfo, iInfo.getNumber(), false).getIndirectReference();
262        }
263        else {
264            info = addToBody(newInfo, false).getIndirectReference();
265        }
266        // XMP
267        byte[] altMetadata = null;
268        PdfObject xmpo = PdfReader.getPdfObject(catalog.get(PdfName.METADATA));
269        if (xmpo != null && xmpo.isStream()) {
270            altMetadata = PdfReader.getStreamBytesRaw((PRStream)xmpo);
271            PdfReader.killIndirect(catalog.get(PdfName.METADATA));
272        }
273        if (xmpMetadata != null) {
274                altMetadata = xmpMetadata;
275        }
276        if (altMetadata != null) {
277                PdfStream xmp;
278                try {
279                        XmpReader xmpr;
280                        if (moreInfo == null) {
281                                xmpr = new XmpReader(altMetadata);
282                                if (!(xmpr.replaceNode("http://ns.adobe.com/pdf/1.3/", "Producer", producer)
283                                                || xmpr.replaceDescriptionAttribute("http://ns.adobe.com/pdf/1.3/", "Producer", producer)))
284                                        xmpr.add("rdf:Description", "http://ns.adobe.com/pdf/1.3/", "pdf:Producer", producer);
285                                if (!(xmpr.replaceNode("http://ns.adobe.com/xap/1.0/", "ModifyDate", date.getW3CDate())
286                                                || xmpr.replaceDescriptionAttribute("http://ns.adobe.com/xap/1.0/", "ModifyDate", date.getW3CDate())))
287                                        xmpr.add("rdf:Description", "http://ns.adobe.com/xap/1.0/", "xmp:ModifyDate", date.getW3CDate());
288                                if (!(xmpr.replaceNode("http://ns.adobe.com/xap/1.0/", "MetadataDate", date.getW3CDate())
289                                                || xmpr.replaceDescriptionAttribute("http://ns.adobe.com/xap/1.0/", "MetadataDate", date.getW3CDate()))) {
290                                }
291                        }
292                        else {
293                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
294                    try {
295                        XmpWriter xmpw = new XmpWriter(baos, newInfo, getPDFXConformance());
296                        xmpw.close();
297                    }
298                    catch (IOException ioe) {
299                        ioe.printStackTrace();
300                    }
301                                xmpr = new XmpReader(baos.toByteArray());
302                        }
303                xmp = new PdfStream(xmpr.serializeDoc());
304                }
305                catch(SAXException e) {
306                        xmp = new PdfStream(altMetadata);
307                }
308                catch(IOException e) {
309                        xmp = new PdfStream(altMetadata);
310                }
311                xmp.put(PdfName.TYPE, PdfName.METADATA);
312                xmp.put(PdfName.SUBTYPE, PdfName.XML);
313                if (crypto != null && !crypto.isMetadataEncrypted()) {
314                        PdfArray ar = new PdfArray();
315                        ar.add(PdfName.CRYPT);
316                        xmp.put(PdfName.FILTER, ar);
317                }
318                if (append && xmpo != null) {
319                        body.add(xmp, xmpo.getIndRef());
320                }
321                else {
322                        catalog.put(PdfName.METADATA, body.add(xmp).getIndirectReference());
323                        markUsed(catalog);
324                }
325        }
326        try {
327            file.reOpen();
328            alterContents();
329            int rootN = ((PRIndirectReference)reader.trailer.get(PdfName.ROOT)).getNumber();
330            if (append) {
331                int keys[] = marked.getKeys();
332                for (int k = 0; k < keys.length; ++k) {
333                    int j = keys[k];
334                    PdfObject obj = reader.getPdfObjectRelease(j);
335                    if (obj != null && skipInfo != j && j < initialXrefSize) {
336                        addToBody(obj, j, j != rootN);
337                    }
338                }
339                for (int k = initialXrefSize; k < reader.getXrefSize(); ++k) {
340                    PdfObject obj = reader.getPdfObject(k);
341                    if (obj != null) {
342                        addToBody(obj, getNewObjectNumber(reader, k, 0));
343                    }
344                }
345            }
346            else {
347                for (int k = 1; k < reader.getXrefSize(); ++k) {
348                    PdfObject obj = reader.getPdfObjectRelease(k);
349                    if (obj != null && skipInfo != k) {
350                        addToBody(obj, getNewObjectNumber(reader, k, 0), k != rootN);
351                    }
352                }
353            }
354        }
355        finally {
356            try {
357                file.close();
358            }
359            catch (Exception e) {
360                // empty on purpose
361            }
362        }
363        PdfIndirectReference encryption = null;
364        PdfObject fileID = null;
365        if (crypto != null) {
366            if (append) {
367                encryption = reader.getCryptoRef();
368            }
369            else {
370                PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false);
371                encryption = encryptionObject.getIndirectReference();
372            }
373            fileID = crypto.getFileID();
374        }
375        else
376            fileID = PdfEncryption.createInfoId(PdfEncryption.createDocumentId());
377        PRIndirectReference iRoot = (PRIndirectReference)reader.trailer.get(PdfName.ROOT);
378        PdfIndirectReference root = new PdfIndirectReference(0, getNewObjectNumber(reader, iRoot.getNumber(), 0));
379        // write the cross-reference table of the body
380        body.writeCrossReferenceTable(os, root, info, encryption, fileID, prevxref);
381        if (fullCompression) {
382            os.write(getISOBytes("startxref\n"));
383            os.write(getISOBytes(String.valueOf(body.offset())));
384            os.write(getISOBytes("\n%%EOF\n"));
385        }
386        else {
387            PdfTrailer trailer = new PdfTrailer(body.size(),
388            body.offset(),
389            root,
390            info,
391            encryption,
392            fileID, prevxref);
393            trailer.toPdf(this, os);
394        }
395        os.flush();
396        if (isCloseStream())
397            os.close();
398        reader.close();
399    }
400
401    void applyRotation(PdfDictionary pageN, ByteBuffer out) {
402        if (!rotateContents)
403            return;
404        Rectangle page = reader.getPageSizeWithRotation(pageN);
405        int rotation = page.getRotation();
406        switch (rotation) {
407            case 90:
408                out.append(PdfContents.ROTATE90);
409                out.append(page.getTop());
410                out.append(' ').append('0').append(PdfContents.ROTATEFINAL);
411                break;
412            case 180:
413                out.append(PdfContents.ROTATE180);
414                out.append(page.getRight());
415                out.append(' ');
416                out.append(page.getTop());
417                out.append(PdfContents.ROTATEFINAL);
418                break;
419            case 270:
420                out.append(PdfContents.ROTATE270);
421                out.append('0').append(' ');
422                out.append(page.getRight());
423                out.append(PdfContents.ROTATEFINAL);
424                break;
425        }
426    }
427
428    void alterContents() throws IOException {
429        for (Object element : pagesToContent.values()) {
430            PageStamp ps = (PageStamp)element;
431            PdfDictionary pageN = ps.pageN;
432            markUsed(pageN);
433            PdfArray ar = null;
434            PdfObject content = PdfReader.getPdfObject(pageN.get(PdfName.CONTENTS), pageN);
435            if (content == null) {
436                ar = new PdfArray();
437                pageN.put(PdfName.CONTENTS, ar);
438            }
439            else if (content.isArray()) {
440                ar = (PdfArray)content;
441                markUsed(ar);
442            }
443            else if (content.isStream()) {
444                ar = new PdfArray();
445                ar.add(pageN.get(PdfName.CONTENTS));
446                pageN.put(PdfName.CONTENTS, ar);
447            }
448            else {
449                ar = new PdfArray();
450                pageN.put(PdfName.CONTENTS, ar);
451            }
452            ByteBuffer out = new ByteBuffer();
453            if (ps.under != null) {
454                out.append(PdfContents.SAVESTATE);
455                applyRotation(pageN, out);
456                out.append(ps.under.getInternalBuffer());
457                out.append(PdfContents.RESTORESTATE);
458            }
459            if (ps.over != null)
460                out.append(PdfContents.SAVESTATE);
461            PdfStream stream = new PdfStream(out.toByteArray());
462            stream.flateCompress(compressionLevel);
463            ar.addFirst(addToBody(stream).getIndirectReference());
464            out.reset();
465            if (ps.over != null) {
466                out.append(' ');
467                out.append(PdfContents.RESTORESTATE);
468                ByteBuffer buf = ps.over.getInternalBuffer();
469                out.append(buf.getBuffer(), 0, ps.replacePoint);
470                out.append(PdfContents.SAVESTATE);
471                applyRotation(pageN, out);
472                out.append(buf.getBuffer(), ps.replacePoint, buf.size() - ps.replacePoint);
473                out.append(PdfContents.RESTORESTATE);
474                stream = new PdfStream(out.toByteArray());
475                stream.flateCompress(compressionLevel);
476                ar.add(addToBody(stream).getIndirectReference());
477            }
478            alterResources(ps);
479        }
480    }
481
482    void alterResources(PageStamp ps) {
483        ps.pageN.put(PdfName.RESOURCES, ps.pageResources.getResources());
484    }
485
486    @Override
487    protected int getNewObjectNumber(PdfReader reader, int number, int generation) {
488        IntHashtable ref = readers2intrefs.get(reader);
489        if (ref != null) {
490            int n = ref.get(number);
491            if (n == 0) {
492                n = getIndirectReferenceNumber();
493                ref.put(number, n);
494            }
495            return n;
496        }
497        if (currentPdfReaderInstance == null) {
498            if (append && number < initialXrefSize)
499                return number;
500            int n = myXref.get(number);
501            if (n == 0) {
502                n = getIndirectReferenceNumber();
503                myXref.put(number, n);
504            }
505            return n;
506        }
507        else
508            return currentPdfReaderInstance.getNewObjectNumber(number, generation);
509    }
510
511    @Override
512    RandomAccessFileOrArray getReaderFile(PdfReader reader) {
513        if (readers2intrefs.containsKey(reader)) {
514            RandomAccessFileOrArray raf = readers2file.get(reader);
515            if (raf != null)
516                return raf;
517            return reader.getSafeFile();
518        }
519        if (currentPdfReaderInstance == null)
520            return file;
521        else
522            return currentPdfReaderInstance.getReaderFile();
523    }
524
525    /**
526     * @param reader
527     * @param openFile
528     * @throws IOException
529     */
530    public void registerReader(PdfReader reader, boolean openFile) throws IOException {
531        if (readers2intrefs.containsKey(reader))
532            return;
533        readers2intrefs.put(reader, new IntHashtable());
534        if (openFile) {
535            RandomAccessFileOrArray raf = reader.getSafeFile();
536            readers2file.put(reader, raf);
537            raf.reOpen();
538        }
539    }
540
541    /**
542     * @param reader
543     */
544    public void unRegisterReader(PdfReader reader) {
545        if (!readers2intrefs.containsKey(reader))
546            return;
547        readers2intrefs.remove(reader);
548        RandomAccessFileOrArray raf = readers2file.get(reader);
549        if (raf == null)
550            return;
551        readers2file.remove(reader);
552        try{raf.close();}catch(Exception e){}
553    }
554
555    static void findAllObjects(PdfReader reader, PdfObject obj, IntHashtable hits) {
556        if (obj == null)
557            return;
558        switch (obj.type()) {
559            case PdfObject.INDIRECT:
560                PRIndirectReference iref = (PRIndirectReference)obj;
561                if (reader != iref.getReader())
562                    return;
563                if (hits.containsKey(iref.getNumber()))
564                    return;
565                hits.put(iref.getNumber(), 1);
566                findAllObjects(reader, PdfReader.getPdfObject(obj), hits);
567                return;
568            case PdfObject.ARRAY:
569                PdfArray a = (PdfArray)obj;
570                for (int k = 0; k < a.size(); ++k) {
571                    findAllObjects(reader, a.getPdfObject(k), hits);
572                }
573                return;
574            case PdfObject.DICTIONARY:
575            case PdfObject.STREAM:
576                PdfDictionary dic = (PdfDictionary)obj;
577            for (Object element : dic.getKeys()) {
578                PdfName name = (PdfName)element;
579                findAllObjects(reader, dic.get(name), hits);
580            }
581                return;
582        }
583    }
584
585    /**
586     * @param fdf
587     * @throws IOException
588     */
589    public void addComments(FdfReader fdf) throws IOException{
590        if (readers2intrefs.containsKey(fdf))
591            return;
592        PdfDictionary catalog = fdf.getCatalog();
593        catalog = catalog.getAsDict(PdfName.FDF);
594        if (catalog == null)
595            return;
596        PdfArray annots = catalog.getAsArray(PdfName.ANNOTS);
597        if (annots == null || annots.size() == 0)
598            return;
599        registerReader(fdf, false);
600        IntHashtable hits = new IntHashtable();
601        HashMap<String, PdfObject> irt = new HashMap<String, PdfObject>();
602        ArrayList<PdfObject> an = new ArrayList<PdfObject>();
603        for (int k = 0; k < annots.size(); ++k) {
604            PdfObject obj = annots.getPdfObject(k);
605            PdfDictionary annot = (PdfDictionary)PdfReader.getPdfObject(obj);
606            PdfNumber page = annot.getAsNumber(PdfName.PAGE);
607            if (page == null || page.intValue() >= reader.getNumberOfPages())
608                continue;
609            findAllObjects(fdf, obj, hits);
610            an.add(obj);
611            if (obj.type() == PdfObject.INDIRECT) {
612                PdfObject nm = PdfReader.getPdfObject(annot.get(PdfName.NM));
613                if (nm != null && nm.type() == PdfObject.STRING)
614                    irt.put(nm.toString(), obj);
615            }
616        }
617        int arhits[] = hits.getKeys();
618        for (int k = 0; k < arhits.length; ++k) {
619            int n = arhits[k];
620            PdfObject obj = fdf.getPdfObject(n);
621            if (obj.type() == PdfObject.DICTIONARY) {
622                PdfObject str = PdfReader.getPdfObject(((PdfDictionary)obj).get(PdfName.IRT));
623                if (str != null && str.type() == PdfObject.STRING) {
624                   PdfObject i = irt.get(str.toString());
625                   if (i != null) {
626                       PdfDictionary dic2 = new PdfDictionary();
627                       dic2.merge((PdfDictionary)obj);
628                       dic2.put(PdfName.IRT, i);
629                       obj = dic2;
630                   }
631                }
632            }
633            addToBody(obj, getNewObjectNumber(fdf, n, 0));
634        }
635        for (int k = 0; k < an.size(); ++k) {
636            PdfObject obj = an.get(k);
637            PdfDictionary annot = (PdfDictionary)PdfReader.getPdfObject(obj);
638            PdfNumber page = annot.getAsNumber(PdfName.PAGE);
639            PdfDictionary dic = reader.getPageN(page.intValue() + 1);
640            PdfArray annotsp = (PdfArray)PdfReader.getPdfObject(dic.get(PdfName.ANNOTS), dic);
641            if (annotsp == null) {
642                annotsp = new PdfArray();
643                dic.put(PdfName.ANNOTS, annotsp);
644                markUsed(dic);
645            }
646            markUsed(annotsp);
647            annotsp.add(obj);
648        }
649    }
650
651    PageStamp getPageStamp(int pageNum) {
652        PdfDictionary pageN = reader.getPageN(pageNum);
653        PageStamp ps = pagesToContent.get(pageN);
654        if (ps == null) {
655            ps = new PageStamp(this, reader, pageN);
656            pagesToContent.put(pageN, ps);
657        }
658        return ps;
659    }
660
661    PdfContentByte getUnderContent(int pageNum) {
662        if (pageNum < 1 || pageNum > reader.getNumberOfPages())
663            return null;
664        PageStamp ps = getPageStamp(pageNum);
665        if (ps.under == null)
666            ps.under = new StampContent(this, ps);
667        return ps.under;
668    }
669
670    PdfContentByte getOverContent(int pageNum) {
671        if (pageNum < 1 || pageNum > reader.getNumberOfPages())
672            return null;
673        PageStamp ps = getPageStamp(pageNum);
674        if (ps.over == null)
675            ps.over = new StampContent(this, ps);
676        return ps.over;
677    }
678
679    void correctAcroFieldPages(int page) {
680        if (acroFields == null)
681            return;
682        if (page > reader.getNumberOfPages())
683            return;
684        Map<String, Item> fields = acroFields.getFields();
685        for (AcroFields.Item item: fields.values()) {
686            for (int k = 0; k < item.size(); ++k) {
687                int p = item.getPage(k).intValue();
688                if (p >= page)
689                    item.forcePage(k, p + 1);
690            }
691        }
692    }
693
694    private static void moveRectangle(PdfDictionary dic2, PdfReader r, int pageImported, PdfName key, String name) {
695        Rectangle m = r.getBoxSize(pageImported, name);
696        if (m == null)
697            dic2.remove(key);
698        else
699            dic2.put(key, new PdfRectangle(m));
700    }
701
702    void replacePage(PdfReader r, int pageImported, int pageReplaced) {
703        PdfDictionary pageN = reader.getPageN(pageReplaced);
704        if (pagesToContent.containsKey(pageN))
705            throw new IllegalStateException(MessageLocalization.getComposedMessage("this.page.cannot.be.replaced.new.content.was.already.added"));
706        PdfImportedPage p = getImportedPage(r, pageImported);
707        PdfDictionary dic2 = reader.getPageNRelease(pageReplaced);
708        dic2.remove(PdfName.RESOURCES);
709        dic2.remove(PdfName.CONTENTS);
710        moveRectangle(dic2, r, pageImported, PdfName.MEDIABOX, "media");
711        moveRectangle(dic2, r, pageImported, PdfName.CROPBOX, "crop");
712        moveRectangle(dic2, r, pageImported, PdfName.TRIMBOX, "trim");
713        moveRectangle(dic2, r, pageImported, PdfName.ARTBOX, "art");
714        moveRectangle(dic2, r, pageImported, PdfName.BLEEDBOX, "bleed");
715        dic2.put(PdfName.ROTATE, new PdfNumber(r.getPageRotation(pageImported)));
716        PdfContentByte cb = getOverContent(pageReplaced);
717        cb.addTemplate(p, 0, 0);
718        PageStamp ps = pagesToContent.get(pageN);
719        ps.replacePoint = ps.over.getInternalBuffer().size();
720    }
721
722    void insertPage(int pageNumber, Rectangle mediabox) {
723        Rectangle media = new Rectangle(mediabox);
724        int rotation = media.getRotation() % 360;
725        PdfDictionary page = new PdfDictionary(PdfName.PAGE);
726        PdfDictionary resources = new PdfDictionary();
727        PdfArray procset = new PdfArray();
728        procset.add(PdfName.PDF);
729        procset.add(PdfName.TEXT);
730        procset.add(PdfName.IMAGEB);
731        procset.add(PdfName.IMAGEC);
732        procset.add(PdfName.IMAGEI);
733        resources.put(PdfName.PROCSET, procset);
734        page.put(PdfName.RESOURCES, resources);
735        page.put(PdfName.ROTATE, new PdfNumber(rotation));
736        page.put(PdfName.MEDIABOX, new PdfRectangle(media, rotation));
737        PRIndirectReference pref = reader.addPdfObject(page);
738        PdfDictionary parent;
739        PRIndirectReference parentRef;
740        if (pageNumber > reader.getNumberOfPages()) {
741            PdfDictionary lastPage = reader.getPageNRelease(reader.getNumberOfPages());
742            parentRef = (PRIndirectReference)lastPage.get(PdfName.PARENT);
743            parentRef = new PRIndirectReference(reader, parentRef.getNumber());
744            parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
745            PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
746            kids.add(pref);
747            markUsed(kids);
748            reader.pageRefs.insertPage(pageNumber, pref);
749        }
750        else {
751            if (pageNumber < 1)
752                pageNumber = 1;
753            PdfDictionary firstPage = reader.getPageN(pageNumber);
754            PRIndirectReference firstPageRef = reader.getPageOrigRef(pageNumber);
755            reader.releasePage(pageNumber);
756            parentRef = (PRIndirectReference)firstPage.get(PdfName.PARENT);
757            parentRef = new PRIndirectReference(reader, parentRef.getNumber());
758            parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
759            PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
760            int len = kids.size();
761            int num = firstPageRef.getNumber();
762            for (int k = 0; k < len; ++k) {
763                PRIndirectReference cur = (PRIndirectReference)kids.getPdfObject(k);
764                if (num == cur.getNumber()) {
765                    kids.add(k, pref);
766                    break;
767                }
768            }
769            if (len == kids.size())
770                throw new RuntimeException(MessageLocalization.getComposedMessage("internal.inconsistence"));
771            markUsed(kids);
772            reader.pageRefs.insertPage(pageNumber, pref);
773            correctAcroFieldPages(pageNumber);
774        }
775        page.put(PdfName.PARENT, parentRef);
776        while (parent != null) {
777            markUsed(parent);
778            PdfNumber count = (PdfNumber)PdfReader.getPdfObjectRelease(parent.get(PdfName.COUNT));
779            parent.put(PdfName.COUNT, new PdfNumber(count.intValue() + 1));
780            parent = parent.getAsDict(PdfName.PARENT);
781        }
782    }
783
784    /** Getter for property rotateContents.
785     * @return Value of property rotateContents.
786     *
787     */
788    boolean isRotateContents() {
789        return this.rotateContents;
790    }
791
792    /** Setter for property rotateContents.
793     * @param rotateContents New value of property rotateContents.
794     *
795     */
796    void setRotateContents(boolean rotateContents) {
797        this.rotateContents = rotateContents;
798    }
799
800    boolean isContentWritten() {
801        return body.size() > 1;
802    }
803
804    AcroFields getAcroFields() {
805        if (acroFields == null) {
806            acroFields = new AcroFields(reader, this);
807        }
808        return acroFields;
809    }
810
811    void setFormFlattening(boolean flat) {
812        this.flat = flat;
813    }
814
815        void setFreeTextFlattening(boolean flat) {
816                this.flatFreeText = flat;
817    }
818
819    boolean partialFormFlattening(String name) {
820        getAcroFields();
821        if (acroFields.getXfa().isXfaPresent())
822            throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("partial.form.flattening.is.not.supported.with.xfa.forms"));
823        if (!acroFields.getFields().containsKey(name))
824            return false;
825        partialFlattening.add(name);
826        return true;
827    }
828
829    void flatFields() {
830        if (append)
831            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("field.flattening.is.not.supported.in.append.mode"));
832        getAcroFields();
833        Map<String, Item> fields = acroFields.getFields();
834        if (fieldsAdded && partialFlattening.isEmpty()) {
835            for (String s: fields.keySet()) {
836                partialFlattening.add(s);
837            }
838        }
839        PdfDictionary acroForm = reader.getCatalog().getAsDict(PdfName.ACROFORM);
840        PdfArray acroFds = null;
841        if (acroForm != null) {
842            acroFds = (PdfArray)PdfReader.getPdfObject(acroForm.get(PdfName.FIELDS), acroForm);
843        }
844        for (Map.Entry<String, Item> entry: fields.entrySet()) {
845            String name = entry.getKey();
846            if (!partialFlattening.isEmpty() && !partialFlattening.contains(name))
847                continue;
848            AcroFields.Item item = entry.getValue();
849            for (int k = 0; k < item.size(); ++k) {
850                PdfDictionary merged = item.getMerged(k);
851                PdfNumber ff = merged.getAsNumber(PdfName.F);
852                int flags = 0;
853                if (ff != null)
854                    flags = ff.intValue();
855                int page = item.getPage(k).intValue();
856                PdfDictionary appDic = merged.getAsDict(PdfName.AP);
857                if (appDic != null && (flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) == 0) {
858                    PdfObject obj = appDic.get(PdfName.N);
859                    PdfAppearance app = null;
860                    if (obj != null) {
861                        PdfObject objReal = PdfReader.getPdfObject(obj);
862                        if (obj instanceof PdfIndirectReference && !obj.isIndirect())
863                            app = new PdfAppearance((PdfIndirectReference)obj);
864                        else if (objReal instanceof PdfStream) {
865                            ((PdfDictionary)objReal).put(PdfName.SUBTYPE, PdfName.FORM);
866                            app = new PdfAppearance((PdfIndirectReference)obj);
867                        }
868                        else {
869                            if (objReal != null && objReal.isDictionary()) {
870                                PdfName as = merged.getAsName(PdfName.AS);
871                                if (as != null) {
872                                    PdfIndirectReference iref = (PdfIndirectReference)((PdfDictionary)objReal).get(as);
873                                    if (iref != null) {
874                                        app = new PdfAppearance(iref);
875                                        if (iref.isIndirect()) {
876                                            objReal = PdfReader.getPdfObject(iref);
877                                            ((PdfDictionary)objReal).put(PdfName.SUBTYPE, PdfName.FORM);
878                                        }
879                                    }
880                                }
881                            }
882                        }
883                    }
884                    if (app != null) {
885                        Rectangle box = PdfReader.getNormalizedRectangle(merged.getAsArray(PdfName.RECT));
886                        PdfContentByte cb = getOverContent(page);
887                        cb.setLiteral("Q ");
888                        cb.addTemplate(app, box.getLeft(), box.getBottom());
889                        cb.setLiteral("q ");
890                    }
891                }
892                if (partialFlattening.isEmpty())
893                    continue;
894                PdfDictionary pageDic = reader.getPageN(page);
895                PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
896                if (annots == null)
897                    continue;
898                for (int idx = 0; idx < annots.size(); ++idx) {
899                    PdfObject ran = annots.getPdfObject(idx);
900                    if (!ran.isIndirect())
901                        continue;
902                    PdfObject ran2 = item.getWidgetRef(k);
903                    if (!ran2.isIndirect())
904                        continue;
905                    if (((PRIndirectReference)ran).getNumber() == ((PRIndirectReference)ran2).getNumber()) {
906                        annots.remove(idx--);
907                        PRIndirectReference wdref = (PRIndirectReference)ran2;
908                        while (true) {
909                            PdfDictionary wd = (PdfDictionary)PdfReader.getPdfObject(wdref);
910                            PRIndirectReference parentRef = (PRIndirectReference)wd.get(PdfName.PARENT);
911                            PdfReader.killIndirect(wdref);
912                            if (parentRef == null) { // reached AcroForm
913                                for (int fr = 0; fr < acroFds.size(); ++fr) {
914                                    PdfObject h = acroFds.getPdfObject(fr);
915                                    if (h.isIndirect() && ((PRIndirectReference)h).getNumber() == wdref.getNumber()) {
916                                        acroFds.remove(fr);
917                                        --fr;
918                                    }
919                                }
920                                break;
921                            }
922                            PdfDictionary parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
923                            PdfArray kids = parent.getAsArray(PdfName.KIDS);
924                            for (int fr = 0; fr < kids.size(); ++fr) {
925                                PdfObject h = kids.getPdfObject(fr);
926                                if (h.isIndirect() && ((PRIndirectReference)h).getNumber() == wdref.getNumber()) {
927                                    kids.remove(fr);
928                                    --fr;
929                                }
930                            }
931                            if (!kids.isEmpty())
932                                break;
933                            wdref = parentRef;
934                        }
935                    }
936                }
937                if (annots.isEmpty()) {
938                    PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
939                    pageDic.remove(PdfName.ANNOTS);
940                }
941            }
942        }
943        if (!fieldsAdded && partialFlattening.isEmpty()) {
944            for (int page = 1; page <= reader.getNumberOfPages(); ++page) {
945                PdfDictionary pageDic = reader.getPageN(page);
946                PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
947                if (annots == null)
948                    continue;
949                for (int idx = 0; idx < annots.size(); ++idx) {
950                    PdfObject annoto = annots.getDirectObject(idx);
951                    if (annoto instanceof PdfIndirectReference && !annoto.isIndirect())
952                        continue;
953                    if (!annoto.isDictionary() || PdfName.WIDGET.equals(((PdfDictionary)annoto).get(PdfName.SUBTYPE))) {
954                        annots.remove(idx);
955                        --idx;
956                    }
957                }
958                if (annots.isEmpty()) {
959                    PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
960                    pageDic.remove(PdfName.ANNOTS);
961                }
962            }
963            eliminateAcroformObjects();
964        }
965    }
966
967    void eliminateAcroformObjects() {
968        PdfObject acro = reader.getCatalog().get(PdfName.ACROFORM);
969        if (acro == null)
970            return;
971        PdfDictionary acrodic = (PdfDictionary)PdfReader.getPdfObject(acro);
972        reader.killXref(acrodic.get(PdfName.XFA));
973        acrodic.remove(PdfName.XFA);
974        PdfObject iFields = acrodic.get(PdfName.FIELDS);
975        if (iFields != null) {
976            PdfDictionary kids = new PdfDictionary();
977            kids.put(PdfName.KIDS, iFields);
978            sweepKids(kids);
979            PdfReader.killIndirect(iFields);
980            acrodic.put(PdfName.FIELDS, new PdfArray());
981        }
982        acrodic.remove(PdfName.SIGFLAGS);
983//        PdfReader.killIndirect(acro);
984//        reader.getCatalog().remove(PdfName.ACROFORM);
985    }
986
987    void sweepKids(PdfObject obj) {
988        PdfObject oo = PdfReader.killIndirect(obj);
989        if (oo == null || !oo.isDictionary())
990            return;
991        PdfDictionary dic = (PdfDictionary)oo;
992        PdfArray kids = (PdfArray)PdfReader.killIndirect(dic.get(PdfName.KIDS));
993        if (kids == null)
994            return;
995        for (int k = 0; k < kids.size(); ++k) {
996            sweepKids(kids.getPdfObject(k));
997        }
998    }
999
1000    private void flatFreeTextFields()
1001        {
1002                if (append)
1003                        throw new IllegalArgumentException(MessageLocalization.getComposedMessage("freetext.flattening.is.not.supported.in.append.mode"));
1004
1005                for (int page = 1; page <= reader.getNumberOfPages(); ++page)
1006                {
1007                        PdfDictionary pageDic = reader.getPageN(page);
1008                        PdfArray annots = pageDic.getAsArray(PdfName.ANNOTS);
1009                        if (annots == null)
1010                                continue;
1011                        for (int idx = 0; idx < annots.size(); ++idx)
1012                        {
1013                                PdfObject annoto = annots.getDirectObject(idx);
1014                                if (annoto instanceof PdfIndirectReference && !annoto.isIndirect())
1015                                        continue;
1016
1017                                PdfDictionary annDic = (PdfDictionary)annoto;
1018                                if (!((PdfName)annDic.get(PdfName.SUBTYPE)).equals(PdfName.FREETEXT))
1019                                        continue;
1020                                PdfNumber ff = annDic.getAsNumber(PdfName.F);
1021                int flags = ff != null ? ff.intValue() : 0;
1022
1023                                if ( (flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) == 0)
1024                                {
1025                                        PdfObject obj1 = annDic.get(PdfName.AP);
1026                                        if (obj1 == null)
1027                                                continue;
1028                                        PdfDictionary appDic = obj1 instanceof PdfIndirectReference ?
1029                                                        (PdfDictionary) PdfReader.getPdfObject(obj1) : (PdfDictionary) obj1;
1030                                        PdfObject obj = appDic.get(PdfName.N);
1031                                        PdfAppearance app = null;
1032                                        PdfObject objReal = PdfReader.getPdfObject(obj);
1033
1034                                        if (obj instanceof PdfIndirectReference && !obj.isIndirect())
1035                                                app = new PdfAppearance((PdfIndirectReference)obj);
1036                                        else if (objReal instanceof PdfStream)
1037                                        {
1038                                                ((PdfDictionary)objReal).put(PdfName.SUBTYPE, PdfName.FORM);
1039                                                app = new PdfAppearance((PdfIndirectReference)obj);
1040                                        }
1041                                        else
1042                                        {
1043                                                if (objReal.isDictionary())
1044                                                {
1045                                                        PdfName as_p = appDic.getAsName(PdfName.AS);
1046                                                        if (as_p != null)
1047                                                        {
1048                                                                PdfIndirectReference iref = (PdfIndirectReference)((PdfDictionary)objReal).get(as_p);
1049                                                                if (iref != null)
1050                                                                {
1051                                                                        app = new PdfAppearance(iref);
1052                                                                        if (iref.isIndirect())
1053                                                                        {
1054                                                                                objReal = PdfReader.getPdfObject(iref);
1055                                                                                ((PdfDictionary)objReal).put(PdfName.SUBTYPE, PdfName.FORM);
1056                                                                        }
1057                                                                }
1058                                                        }
1059                                                }
1060                                        }
1061                                        if (app != null)
1062                                        {
1063                                                Rectangle box = PdfReader.getNormalizedRectangle(annDic.getAsArray(PdfName.RECT));
1064                                                PdfContentByte cb = getOverContent(page);
1065                                                cb.setLiteral("Q ");
1066                                                cb.addTemplate(app, box.getLeft(), box.getBottom());
1067                                                cb.setLiteral("q ");
1068                                        }
1069                                }
1070                        }
1071                        for (int idx = 0; idx < annots.size(); ++idx)
1072                        {
1073                            PdfDictionary annot = annots.getAsDict(idx);
1074                                if (annot != null)
1075                                {
1076                                        if (PdfName.FREETEXT.equals(annot.get(PdfName.SUBTYPE)))
1077                                        {
1078                                            annots.remove(idx);
1079                                                --idx;
1080                                        }
1081                                }
1082                        }
1083                        if (annots.isEmpty())
1084                        {
1085                                PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
1086                                pageDic.remove(PdfName.ANNOTS);
1087                        }
1088                }
1089        }
1090
1091    /**
1092     * @see com.itextpdf.text.pdf.PdfWriter#getPageReference(int)
1093     */
1094    @Override
1095    public PdfIndirectReference getPageReference(int page) {
1096        PdfIndirectReference ref = reader.getPageOrigRef(page);
1097        if (ref == null)
1098            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("invalid.page.number.1", page));
1099        return ref;
1100    }
1101
1102    /**
1103     * @see com.itextpdf.text.pdf.PdfWriter#addAnnotation(com.itextpdf.text.pdf.PdfAnnotation)
1104     */
1105    @Override
1106    public void addAnnotation(PdfAnnotation annot) {
1107        throw new RuntimeException(MessageLocalization.getComposedMessage("unsupported.in.this.context.use.pdfstamper.addannotation"));
1108    }
1109
1110    void addDocumentField(PdfIndirectReference ref) {
1111        PdfDictionary catalog = reader.getCatalog();
1112        PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), catalog);
1113        if (acroForm == null) {
1114            acroForm = new PdfDictionary();
1115            catalog.put(PdfName.ACROFORM, acroForm);
1116            markUsed(catalog);
1117        }
1118        PdfArray fields = (PdfArray)PdfReader.getPdfObject(acroForm.get(PdfName.FIELDS), acroForm);
1119        if (fields == null) {
1120            fields = new PdfArray();
1121            acroForm.put(PdfName.FIELDS, fields);
1122            markUsed(acroForm);
1123        }
1124        if (!acroForm.contains(PdfName.DA)) {
1125            acroForm.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
1126            markUsed(acroForm);
1127        }
1128        fields.add(ref);
1129        markUsed(fields);
1130    }
1131
1132    void addFieldResources() throws IOException {
1133        if (fieldTemplates.isEmpty())
1134            return;
1135        PdfDictionary catalog = reader.getCatalog();
1136        PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), catalog);
1137        if (acroForm == null) {
1138            acroForm = new PdfDictionary();
1139            catalog.put(PdfName.ACROFORM, acroForm);
1140            markUsed(catalog);
1141        }
1142        PdfDictionary dr = (PdfDictionary)PdfReader.getPdfObject(acroForm.get(PdfName.DR), acroForm);
1143        if (dr == null) {
1144            dr = new PdfDictionary();
1145            acroForm.put(PdfName.DR, dr);
1146            markUsed(acroForm);
1147        }
1148        markUsed(dr);
1149        for (PdfTemplate template: fieldTemplates) {
1150            PdfFormField.mergeResources(dr, (PdfDictionary)template.getResources(), this);
1151        }
1152        // if (dr.get(PdfName.ENCODING) == null) dr.put(PdfName.ENCODING, PdfName.WIN_ANSI_ENCODING);
1153        PdfDictionary fonts = dr.getAsDict(PdfName.FONT);
1154        if (fonts == null) {
1155            fonts = new PdfDictionary();
1156            dr.put(PdfName.FONT, fonts);
1157        }
1158        if (!fonts.contains(PdfName.HELV)) {
1159            PdfDictionary dic = new PdfDictionary(PdfName.FONT);
1160            dic.put(PdfName.BASEFONT, PdfName.HELVETICA);
1161            dic.put(PdfName.ENCODING, PdfName.WIN_ANSI_ENCODING);
1162            dic.put(PdfName.NAME, PdfName.HELV);
1163            dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
1164            fonts.put(PdfName.HELV, addToBody(dic).getIndirectReference());
1165        }
1166        if (!fonts.contains(PdfName.ZADB)) {
1167            PdfDictionary dic = new PdfDictionary(PdfName.FONT);
1168            dic.put(PdfName.BASEFONT, PdfName.ZAPFDINGBATS);
1169            dic.put(PdfName.NAME, PdfName.ZADB);
1170            dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
1171            fonts.put(PdfName.ZADB, addToBody(dic).getIndirectReference());
1172        }
1173        if (acroForm.get(PdfName.DA) == null) {
1174            acroForm.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
1175            markUsed(acroForm);
1176        }
1177    }
1178
1179    void expandFields(PdfFormField field, ArrayList<PdfAnnotation> allAnnots) {
1180        allAnnots.add(field);
1181        ArrayList<PdfFormField> kids = field.getKids();
1182        if (kids != null) {
1183            for (int k = 0; k < kids.size(); ++k)
1184                expandFields(kids.get(k), allAnnots);
1185        }
1186    }
1187
1188    void addAnnotation(PdfAnnotation annot, PdfDictionary pageN) {
1189        try {
1190            ArrayList<PdfAnnotation> allAnnots = new ArrayList<PdfAnnotation>();
1191            if (annot.isForm()) {
1192                fieldsAdded = true;
1193                getAcroFields();
1194                PdfFormField field = (PdfFormField)annot;
1195                if (field.getParent() != null)
1196                    return;
1197                expandFields(field, allAnnots);
1198            }
1199            else
1200                allAnnots.add(annot);
1201            for (int k = 0; k < allAnnots.size(); ++k) {
1202                annot = allAnnots.get(k);
1203                if (annot.getPlaceInPage() > 0)
1204                    pageN = reader.getPageN(annot.getPlaceInPage());
1205                if (annot.isForm()) {
1206                    if (!annot.isUsed()) {
1207                        HashSet<PdfTemplate> templates = annot.getTemplates();
1208                        if (templates != null)
1209                            fieldTemplates.addAll(templates);
1210                    }
1211                    PdfFormField field = (PdfFormField)annot;
1212                    if (field.getParent() == null)
1213                        addDocumentField(field.getIndirectReference());
1214                }
1215                if (annot.isAnnotation()) {
1216                    PdfObject pdfobj = PdfReader.getPdfObject(pageN.get(PdfName.ANNOTS), pageN);
1217                    PdfArray annots = null;
1218                    if (pdfobj == null || !pdfobj.isArray()) {
1219                        annots = new PdfArray();
1220                        pageN.put(PdfName.ANNOTS, annots);
1221                        markUsed(pageN);
1222                    }
1223                    else
1224                       annots = (PdfArray)pdfobj;
1225                    annots.add(annot.getIndirectReference());
1226                    markUsed(annots);
1227                    if (!annot.isUsed()) {
1228                        PdfRectangle rect = (PdfRectangle)annot.get(PdfName.RECT);
1229                        if (rect != null && (rect.left() != 0 || rect.right() != 0 || rect.top() != 0 || rect.bottom() != 0)) {
1230                            int rotation = reader.getPageRotation(pageN);
1231                            Rectangle pageSize = reader.getPageSizeWithRotation(pageN);
1232                            switch (rotation) {
1233                                case 90:
1234                                    annot.put(PdfName.RECT, new PdfRectangle(
1235                                    pageSize.getTop() - rect.top(),
1236                                    rect.right(),
1237                                    pageSize.getTop() - rect.bottom(),
1238                                    rect.left()));
1239                                    break;
1240                                case 180:
1241                                    annot.put(PdfName.RECT, new PdfRectangle(
1242                                    pageSize.getRight() - rect.left(),
1243                                    pageSize.getTop() - rect.bottom(),
1244                                    pageSize.getRight() - rect.right(),
1245                                    pageSize.getTop() - rect.top()));
1246                                    break;
1247                                case 270:
1248                                    annot.put(PdfName.RECT, new PdfRectangle(
1249                                    rect.bottom(),
1250                                    pageSize.getRight() - rect.left(),
1251                                    rect.top(),
1252                                    pageSize.getRight() - rect.right()));
1253                                    break;
1254                            }
1255                        }
1256                    }
1257                }
1258                if (!annot.isUsed()) {
1259                    annot.setUsed();
1260                    addToBody(annot, annot.getIndirectReference());
1261                }
1262            }
1263        }
1264        catch (IOException e) {
1265            throw new ExceptionConverter(e);
1266        }
1267    }
1268
1269    @Override
1270    void addAnnotation(PdfAnnotation annot, int page) {
1271        annot.setPage(page);
1272        addAnnotation(annot, reader.getPageN(page));
1273    }
1274
1275    private void outlineTravel(PRIndirectReference outline) {
1276        while (outline != null) {
1277            PdfDictionary outlineR = (PdfDictionary)PdfReader.getPdfObjectRelease(outline);
1278            PRIndirectReference first = (PRIndirectReference)outlineR.get(PdfName.FIRST);
1279            if (first != null) {
1280                outlineTravel(first);
1281            }
1282            PdfReader.killIndirect(outlineR.get(PdfName.DEST));
1283            PdfReader.killIndirect(outlineR.get(PdfName.A));
1284            PdfReader.killIndirect(outline);
1285            outline = (PRIndirectReference)outlineR.get(PdfName.NEXT);
1286        }
1287    }
1288
1289    void deleteOutlines() {
1290        PdfDictionary catalog = reader.getCatalog();
1291        PRIndirectReference outlines = (PRIndirectReference)catalog.get(PdfName.OUTLINES);
1292        if (outlines == null)
1293            return;
1294        outlineTravel(outlines);
1295        PdfReader.killIndirect(outlines);
1296        catalog.remove(PdfName.OUTLINES);
1297        markUsed(catalog);
1298    }
1299
1300    void setJavaScript() throws IOException {
1301        HashMap<String, PdfObject> djs = pdf.getDocumentLevelJS();
1302        if (djs.isEmpty())
1303            return;
1304        PdfDictionary catalog = reader.getCatalog();
1305        PdfDictionary names = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.NAMES), catalog);
1306        if (names == null) {
1307            names = new PdfDictionary();
1308            catalog.put(PdfName.NAMES, names);
1309            markUsed(catalog);
1310        }
1311        markUsed(names);
1312        PdfDictionary tree = PdfNameTree.writeTree(djs, this);
1313        names.put(PdfName.JAVASCRIPT, addToBody(tree).getIndirectReference());
1314    }
1315
1316    void addFileAttachments() throws IOException {
1317        HashMap<String, PdfObject> fs = pdf.getDocumentFileAttachment();
1318        if (fs.isEmpty())
1319            return;
1320        PdfDictionary catalog = reader.getCatalog();
1321        PdfDictionary names = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.NAMES), catalog);
1322        if (names == null) {
1323            names = new PdfDictionary();
1324            catalog.put(PdfName.NAMES, names);
1325            markUsed(catalog);
1326        }
1327        markUsed(names);
1328        HashMap<String, PdfObject> old = PdfNameTree.readTree((PdfDictionary)PdfReader.getPdfObjectRelease(names.get(PdfName.EMBEDDEDFILES)));
1329        for (Map.Entry<String, PdfObject> entry: fs.entrySet()) {
1330            String name = entry.getKey();
1331            int k = 0;
1332            StringBuilder nn = new StringBuilder(name);
1333            while (old.containsKey(nn.toString())) {
1334                ++k;
1335                nn.append(" ").append(k);
1336            }
1337            old.put(nn.toString(), entry.getValue());
1338        }
1339        PdfDictionary tree = PdfNameTree.writeTree(old, this);
1340        // Remove old EmbeddedFiles object if preset
1341        PdfObject oldEmbeddedFiles = names.get(PdfName.EMBEDDEDFILES);
1342        if (oldEmbeddedFiles != null) {
1343            PdfReader.killIndirect(oldEmbeddedFiles);
1344        }
1345
1346        // Add new EmbeddedFiles object
1347        names.put(PdfName.EMBEDDEDFILES, addToBody(tree).getIndirectReference());
1348    }
1349
1350    /**
1351     * Adds or replaces the Collection Dictionary in the Catalog.
1352     * @param   collection      the new collection dictionary.
1353     */
1354    void makePackage( PdfCollection collection ) {
1355        PdfDictionary catalog = reader.getCatalog();
1356        catalog.put( PdfName.COLLECTION, collection );
1357    }
1358
1359    void setOutlines() throws IOException {
1360        if (newBookmarks == null)
1361            return;
1362        deleteOutlines();
1363        if (newBookmarks.isEmpty())
1364            return;
1365        PdfDictionary catalog = reader.getCatalog();
1366        boolean namedAsNames = catalog.get(PdfName.DESTS) != null;
1367        writeOutlines(catalog, namedAsNames);
1368        markUsed(catalog);
1369    }
1370
1371    /**
1372     * Sets the viewer preferences.
1373     * @param preferences the viewer preferences
1374     * @see PdfWriter#setViewerPreferences(int)
1375     */
1376    @Override
1377    public void setViewerPreferences(int preferences) {
1378        useVp = true;
1379        this.viewerPreferences.setViewerPreferences(preferences);
1380    }
1381
1382    /** Adds a viewer preference
1383     * @param key a key for a viewer preference
1384     * @param value the value for the viewer preference
1385     * @see PdfViewerPreferences#addViewerPreference
1386     */
1387    @Override
1388    public void addViewerPreference(PdfName key, PdfObject value) {
1389        useVp = true;
1390        this.viewerPreferences.addViewerPreference(key, value);
1391    }
1392
1393    /**
1394     * Set the signature flags.
1395     * @param f the flags. This flags are ORed with current ones
1396     */
1397    @Override
1398    public void setSigFlags(int f) {
1399        sigFlags |= f;
1400    }
1401
1402    /** Always throws an <code>UnsupportedOperationException</code>.
1403     * @param actionType ignore
1404     * @param action ignore
1405     * @throws PdfException ignore
1406     * @see PdfStamper#setPageAction(PdfName, PdfAction, int)
1407     */
1408    @Override
1409    public void setPageAction(PdfName actionType, PdfAction action) throws PdfException {
1410        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.setpageaction.pdfname.actiontype.pdfaction.action.int.page"));
1411    }
1412
1413    /**
1414     * Sets the open and close page additional action.
1415     * @param actionType the action type. It can be <CODE>PdfWriter.PAGE_OPEN</CODE>
1416     * or <CODE>PdfWriter.PAGE_CLOSE</CODE>
1417     * @param action the action to perform
1418     * @param page the page where the action will be applied. The first page is 1
1419     * @throws PdfException if the action type is invalid
1420     */
1421    void setPageAction(PdfName actionType, PdfAction action, int page) throws PdfException {
1422        if (!actionType.equals(PAGE_OPEN) && !actionType.equals(PAGE_CLOSE))
1423            throw new PdfException(MessageLocalization.getComposedMessage("invalid.page.additional.action.type.1", actionType.toString()));
1424        PdfDictionary pg = reader.getPageN(page);
1425        PdfDictionary aa = (PdfDictionary)PdfReader.getPdfObject(pg.get(PdfName.AA), pg);
1426        if (aa == null) {
1427            aa = new PdfDictionary();
1428            pg.put(PdfName.AA, aa);
1429            markUsed(pg);
1430        }
1431        aa.put(actionType, action);
1432        markUsed(aa);
1433    }
1434
1435    /**
1436     * Always throws an <code>UnsupportedOperationException</code>.
1437     * @param seconds ignore
1438     */
1439    @Override
1440    public void setDuration(int seconds) {
1441        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.setpageaction.pdfname.actiontype.pdfaction.action.int.page"));
1442    }
1443
1444    /**
1445     * Always throws an <code>UnsupportedOperationException</code>.
1446     * @param transition ignore
1447     */
1448    @Override
1449    public void setTransition(PdfTransition transition) {
1450        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.setpageaction.pdfname.actiontype.pdfaction.action.int.page"));
1451    }
1452
1453    /**
1454     * Sets the display duration for the page (for presentations)
1455     * @param seconds   the number of seconds to display the page. A negative value removes the entry
1456     * @param page the page where the duration will be applied. The first page is 1
1457     */
1458    void setDuration(int seconds, int page) {
1459        PdfDictionary pg = reader.getPageN(page);
1460        if (seconds < 0)
1461            pg.remove(PdfName.DUR);
1462        else
1463            pg.put(PdfName.DUR, new PdfNumber(seconds));
1464        markUsed(pg);
1465    }
1466
1467    /**
1468     * Sets the transition for the page
1469     * @param transition   the transition object. A <code>null</code> removes the transition
1470     * @param page the page where the transition will be applied. The first page is 1
1471     */
1472    void setTransition(PdfTransition transition, int page) {
1473        PdfDictionary pg = reader.getPageN(page);
1474        if (transition == null)
1475            pg.remove(PdfName.TRANS);
1476        else
1477            pg.put(PdfName.TRANS, transition.getTransitionDictionary());
1478        markUsed(pg);
1479    }
1480
1481    protected void markUsed(PdfObject obj) {
1482        if (append && obj != null) {
1483            PRIndirectReference ref = null;
1484            if (obj.type() == PdfObject.INDIRECT)
1485                ref = (PRIndirectReference)obj;
1486            else
1487                ref = obj.getIndRef();
1488            if (ref != null)
1489                marked.put(ref.getNumber(), 1);
1490        }
1491    }
1492
1493    protected void markUsed(int num) {
1494        if (append)
1495            marked.put(num, 1);
1496    }
1497
1498    /**
1499     * Getter for property append.
1500     * @return Value of property append.
1501     */
1502    boolean isAppend() {
1503        return append;
1504    }
1505
1506    /** Additional-actions defining the actions to be taken in
1507     * response to various trigger events affecting the document
1508     * as a whole. The actions types allowed are: <CODE>DOCUMENT_CLOSE</CODE>,
1509     * <CODE>WILL_SAVE</CODE>, <CODE>DID_SAVE</CODE>, <CODE>WILL_PRINT</CODE>
1510     * and <CODE>DID_PRINT</CODE>.
1511     *
1512     * @param actionType the action type
1513     * @param action the action to execute in response to the trigger
1514     * @throws PdfException on invalid action type
1515     */
1516    @Override
1517    public void setAdditionalAction(PdfName actionType, PdfAction action) throws PdfException {
1518        if (!(actionType.equals(DOCUMENT_CLOSE) ||
1519        actionType.equals(WILL_SAVE) ||
1520        actionType.equals(DID_SAVE) ||
1521        actionType.equals(WILL_PRINT) ||
1522        actionType.equals(DID_PRINT))) {
1523            throw new PdfException(MessageLocalization.getComposedMessage("invalid.additional.action.type.1", actionType.toString()));
1524        }
1525        PdfDictionary aa = reader.getCatalog().getAsDict(PdfName.AA);
1526        if (aa == null) {
1527            if (action == null)
1528                return;
1529            aa = new PdfDictionary();
1530            reader.getCatalog().put(PdfName.AA, aa);
1531        }
1532        markUsed(aa);
1533        if (action == null)
1534            aa.remove(actionType);
1535        else
1536            aa.put(actionType, action);
1537    }
1538
1539    /**
1540     * @see com.itextpdf.text.pdf.PdfWriter#setOpenAction(com.itextpdf.text.pdf.PdfAction)
1541     */
1542    @Override
1543    public void setOpenAction(PdfAction action) {
1544        openAction = action;
1545    }
1546
1547    /**
1548     * @see com.itextpdf.text.pdf.PdfWriter#setOpenAction(java.lang.String)
1549     */
1550    @Override
1551    public void setOpenAction(String name) {
1552        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("open.actions.by.name.are.not.supported"));
1553    }
1554
1555    /**
1556     * @see com.itextpdf.text.pdf.PdfWriter#setThumbnail(com.itextpdf.text.Image)
1557     */
1558    @Override
1559    public void setThumbnail(com.itextpdf.text.Image image) {
1560        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.pdfstamper.setthumbnail"));
1561    }
1562
1563    void setThumbnail(Image image, int page) throws PdfException, DocumentException {
1564        PdfIndirectReference thumb = getImageReference(addDirectImageSimple(image));
1565        reader.resetReleasePage();
1566        PdfDictionary dic = reader.getPageN(page);
1567        dic.put(PdfName.THUMB, thumb);
1568        reader.resetReleasePage();
1569    }
1570
1571    @Override
1572    public PdfContentByte getDirectContentUnder() {
1573        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.pdfstamper.getundercontent.or.pdfstamper.getovercontent"));
1574    }
1575
1576    @Override
1577    public PdfContentByte getDirectContent() {
1578        throw new UnsupportedOperationException(MessageLocalization.getComposedMessage("use.pdfstamper.getundercontent.or.pdfstamper.getovercontent"));
1579    }
1580
1581    /**
1582     * Reads the OCProperties dictionary from the catalog of the existing document
1583     * and fills the documentOCG, documentOCGorder and OCGRadioGroup variables in PdfWriter.
1584     * Note that the original OCProperties of the existing document can contain more information.
1585     * @since   2.1.2
1586     */
1587    protected void readOCProperties() {
1588        if (!documentOCG.isEmpty()) {
1589                return;
1590        }
1591        PdfDictionary dict = reader.getCatalog().getAsDict(PdfName.OCPROPERTIES);
1592        if (dict == null) {
1593                return;
1594        }
1595        PdfArray ocgs = dict.getAsArray(PdfName.OCGS);
1596        PdfIndirectReference ref;
1597        PdfLayer layer;
1598        HashMap<String, PdfLayer> ocgmap = new HashMap<String, PdfLayer>();
1599        for (Iterator<PdfObject> i = ocgs.listIterator(); i.hasNext(); ) {
1600                ref = (PdfIndirectReference)i.next();
1601                layer = new PdfLayer(null);
1602                layer.setRef(ref);
1603                layer.setOnPanel(false);
1604                        layer.merge((PdfDictionary)PdfReader.getPdfObject(ref));
1605                ocgmap.put(ref.toString(), layer);
1606        }
1607        PdfDictionary d = dict.getAsDict(PdfName.D);
1608        PdfArray off = d.getAsArray(PdfName.OFF);
1609        if (off != null) {
1610                for (Iterator<PdfObject> i = off.listIterator(); i.hasNext(); ) {
1611                        ref = (PdfIndirectReference)i.next();
1612                        layer = ocgmap.get(ref.toString());
1613                        layer.setOn(false);
1614                }
1615        }
1616        PdfArray order = d.getAsArray(PdfName.ORDER);
1617        if (order != null) {
1618                addOrder(null, order, ocgmap);
1619        }
1620        documentOCG.addAll(ocgmap.values());
1621        OCGRadioGroup = d.getAsArray(PdfName.RBGROUPS);
1622        if (OCGRadioGroup == null)
1623                OCGRadioGroup = new PdfArray();
1624        OCGLocked = d.getAsArray(PdfName.LOCKED);
1625        if (OCGLocked == null)
1626                OCGLocked = new PdfArray();
1627    }
1628
1629    /**
1630     * Recursive method to reconstruct the documentOCGorder variable in the writer.
1631     * @param   parent  a parent PdfLayer (can be null)
1632     * @param   arr             an array possibly containing children for the parent PdfLayer
1633     * @param   ocgmap  a HashMap with indirect reference Strings as keys and PdfLayer objects as values.
1634     * @since   2.1.2
1635     */
1636    private void addOrder(PdfLayer parent, PdfArray arr, Map<String, PdfLayer> ocgmap) {
1637        PdfObject obj;
1638        PdfLayer layer;
1639        for (int i = 0; i < arr.size(); i++) {
1640                obj = arr.getPdfObject(i);
1641                if (obj.isIndirect()) {
1642                        layer = ocgmap.get(obj.toString());
1643                        layer.setOnPanel(true);
1644                        registerLayer(layer);
1645                        if (parent != null) {
1646                                parent.addChild(layer);
1647                        }
1648                        if (arr.size() > i + 1 && arr.getPdfObject(i + 1).isArray()) {
1649                                i++;
1650                                addOrder(layer, (PdfArray)arr.getPdfObject(i), ocgmap);
1651                        }
1652                }
1653                else if (obj.isArray()) {
1654                    PdfArray sub = (PdfArray)obj;
1655                        if (sub.isEmpty()) return;
1656                        obj = sub.getPdfObject(0);
1657                        if (obj.isString()) {
1658                                layer = new PdfLayer(obj.toString());
1659                                layer.setOnPanel(true);
1660                                registerLayer(layer);
1661                                if (parent != null) {
1662                                        parent.addChild(layer);
1663                                }
1664                                PdfArray array = new PdfArray();
1665                                for (Iterator<PdfObject> j = sub.listIterator(); j.hasNext(); ) {
1666                                        array.add(j.next());
1667                                }
1668                                addOrder(layer, array, ocgmap);
1669                        }
1670                        else {
1671                                addOrder(parent, (PdfArray)obj, ocgmap);
1672                        }
1673                }
1674        }
1675    }
1676
1677    /**
1678     * Gets the PdfLayer objects in an existing document as a Map
1679     * with the names/titles of the layers as keys.
1680     * @return  a Map with all the PdfLayers in the document (and the name/title of the layer as key)
1681     * @since   2.1.2
1682     */
1683    public Map<String, PdfLayer> getPdfLayers() {
1684        if (documentOCG.isEmpty()) {
1685                readOCProperties();
1686        }
1687        HashMap<String, PdfLayer> map = new HashMap<String, PdfLayer>();
1688        PdfLayer layer;
1689        String key;
1690        for (PdfOCG pdfOCG : documentOCG) {
1691                layer = (PdfLayer)pdfOCG;
1692                if (layer.getTitle() == null) {
1693                        key = layer.getAsString(PdfName.NAME).toString();
1694                }
1695                else {
1696                        key = layer.getTitle();
1697                }
1698                if (map.containsKey(key)) {
1699                        int seq = 2;
1700                        String tmp = key + "(" + seq + ")";
1701                        while (map.containsKey(tmp)) {
1702                                seq++;
1703                                tmp = key + "(" + seq + ")";
1704                        }
1705                        key = tmp;
1706                }
1707                        map.put(key, layer);
1708        }
1709        return map;
1710    }
1711
1712    static class PageStamp {
1713
1714        PdfDictionary pageN;
1715        StampContent under;
1716        StampContent over;
1717        PageResources pageResources;
1718        int replacePoint = 0;
1719
1720        PageStamp(PdfStamperImp stamper, PdfReader reader, PdfDictionary pageN) {
1721            this.pageN = pageN;
1722            pageResources = new PageResources();
1723            PdfDictionary resources = pageN.getAsDict(PdfName.RESOURCES);
1724            pageResources.setOriginalResources(resources, stamper.namePtr);
1725        }
1726    }
1727}