001/*
002 * $Id: PdfWriter.java 4891 2011-06-01 18:22:36Z 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.security.cert.Certificate;
050import java.util.ArrayList;
051import java.util.Arrays;
052import java.util.HashMap;
053import java.util.HashSet;
054import java.util.Iterator;
055import java.util.LinkedHashMap;
056import java.util.List;
057import java.util.Map;
058import java.util.TreeMap;
059import java.util.TreeSet;
060
061import com.itextpdf.text.BaseColor;
062import com.itextpdf.text.DocListener;
063import com.itextpdf.text.DocWriter;
064import com.itextpdf.text.Document;
065import com.itextpdf.text.DocumentException;
066import com.itextpdf.text.ExceptionConverter;
067import com.itextpdf.text.Image;
068import com.itextpdf.text.ImgJBIG2;
069import com.itextpdf.text.ImgWMF;
070import com.itextpdf.text.Rectangle;
071import com.itextpdf.text.error_messages.MessageLocalization;
072import com.itextpdf.text.pdf.collection.PdfCollection;
073import com.itextpdf.text.pdf.events.PdfPageEventForwarder;
074import com.itextpdf.text.pdf.interfaces.PdfAnnotations;
075import com.itextpdf.text.pdf.interfaces.PdfDocumentActions;
076import com.itextpdf.text.pdf.interfaces.PdfEncryptionSettings;
077import com.itextpdf.text.pdf.interfaces.PdfPageActions;
078import com.itextpdf.text.pdf.interfaces.PdfRunDirection;
079import com.itextpdf.text.pdf.interfaces.PdfVersion;
080import com.itextpdf.text.pdf.interfaces.PdfViewerPreferences;
081import com.itextpdf.text.pdf.interfaces.PdfXConformance;
082import com.itextpdf.text.pdf.internal.PdfVersionImp;
083import com.itextpdf.text.pdf.internal.PdfXConformanceImp;
084import com.itextpdf.text.xml.xmp.XmpWriter;
085
086/**
087 * A <CODE>DocWriter</CODE> class for PDF.
088 * <P>
089 * When this <CODE>PdfWriter</CODE> is added
090 * to a certain <CODE>PdfDocument</CODE>, the PDF representation of every Element
091 * added to this Document will be written to the outputstream.</P>
092 */
093
094public class PdfWriter extends DocWriter implements
095        PdfViewerPreferences,
096        PdfEncryptionSettings,
097        PdfVersion,
098        PdfDocumentActions,
099        PdfPageActions,
100        PdfXConformance,
101        PdfRunDirection,
102        PdfAnnotations {
103
104        /**
105         * The highest generation number possible.
106         * @since       iText 2.1.6
107         */
108        public static final int GENERATION_MAX = 65535;
109
110// INNER CLASSES
111
112    /**
113     * This class generates the structure of a PDF document.
114     * <P>
115     * This class covers the third section of Chapter 5 in the 'Portable Document Format
116     * Reference Manual version 1.3' (page 55-60). It contains the body of a PDF document
117     * (section 5.14) and it can also generate a Cross-reference Table (section 5.15).
118     *
119     * @see             PdfWriter
120     * @see             PdfObject
121     * @see             PdfIndirectObject
122     */
123
124    public static class PdfBody {
125
126        // inner classes
127
128        /**
129         * <CODE>PdfCrossReference</CODE> is an entry in the PDF Cross-Reference table.
130         */
131
132        static class PdfCrossReference implements Comparable<PdfCrossReference> {
133
134            // membervariables
135            private final int type;
136
137            /** Byte offset in the PDF file. */
138            private final int offset;
139
140            private final int refnum;
141            /** generation of the object. */
142            private final int generation;
143
144            // constructors
145            /**
146             * Constructs a cross-reference element for a PdfIndirectObject.
147             * @param refnum
148             * @param   offset          byte offset of the object
149             * @param   generation      generation number of the object
150             */
151
152            PdfCrossReference(final int refnum, final int offset, final int generation) {
153                type = 0;
154                this.offset = offset;
155                this.refnum = refnum;
156                this.generation = generation;
157            }
158
159            /**
160             * Constructs a cross-reference element for a PdfIndirectObject.
161             * @param refnum
162             * @param   offset          byte offset of the object
163             */
164
165            PdfCrossReference(final int refnum, final int offset) {
166                type = 1;
167                this.offset = offset;
168                this.refnum = refnum;
169                this.generation = 0;
170            }
171
172            PdfCrossReference(final int type, final int refnum, final int offset, final int generation) {
173                this.type = type;
174                this.offset = offset;
175                this.refnum = refnum;
176                this.generation = generation;
177            }
178
179            int getRefnum() {
180                return refnum;
181            }
182
183            /**
184             * Returns the PDF representation of this <CODE>PdfObject</CODE>.
185             * @param os
186             * @throws IOException
187             */
188
189            public void toPdf(final OutputStream os) throws IOException {
190                StringBuffer off = new StringBuffer("0000000000").append(offset);
191                off.delete(0, off.length() - 10);
192                StringBuffer gen = new StringBuffer("00000").append(generation);
193                gen.delete(0, gen.length() - 5);
194
195                off.append(' ').append(gen).append(generation == GENERATION_MAX ? " f \n" : " n \n");
196                os.write(getISOBytes(off.toString()));
197            }
198
199            /**
200             * Writes PDF syntax to the OutputStream
201             * @param midSize
202             * @param os
203             * @throws IOException
204             */
205            public void toPdf(int midSize, final OutputStream os) throws IOException {
206                os.write((byte)type);
207                while (--midSize >= 0)
208                    os.write((byte)(offset >>> 8 * midSize & 0xff));
209                os.write((byte)(generation >>> 8 & 0xff));
210                os.write((byte)(generation & 0xff));
211            }
212
213            /**
214             * @see java.lang.Comparable#compareTo(java.lang.Object)
215             */
216            public int compareTo(final PdfCrossReference other) {
217                return refnum < other.refnum ? -1 : refnum==other.refnum ? 0 : 1;
218            }
219
220            /**
221             * @see java.lang.Object#equals(java.lang.Object)
222             */
223            @Override
224            public boolean equals(final Object obj) {
225                if (obj instanceof PdfCrossReference) {
226                    PdfCrossReference other = (PdfCrossReference)obj;
227                    return refnum == other.refnum;
228                }
229                else
230                    return false;
231            }
232
233            /**
234             * @see java.lang.Object#hashCode()
235             */
236            @Override
237            public int hashCode() {
238                                return refnum;
239                        }
240
241        }
242
243        private static final int OBJSINSTREAM = 200;
244
245        // membervariables
246
247        /** array containing the cross-reference table of the normal objects. */
248        private final TreeSet<PdfCrossReference> xrefs;
249        private int refnum;
250        /** the current byte position in the body. */
251        private int position;
252        private final PdfWriter writer;
253        private ByteBuffer index;
254        private ByteBuffer streamObjects;
255        private int currentObjNum;
256        private int numObj = 0;
257
258        // constructors
259
260        /**
261         * Constructs a new <CODE>PdfBody</CODE>.
262         * @param writer
263         */
264        PdfBody(final PdfWriter writer) {
265            xrefs = new TreeSet<PdfCrossReference>();
266            xrefs.add(new PdfCrossReference(0, 0, GENERATION_MAX));
267            position = writer.getOs().getCounter();
268            refnum = 1;
269            this.writer = writer;
270        }
271
272        // methods
273
274        void setRefnum(final int refnum) {
275            this.refnum = refnum;
276        }
277
278        private PdfWriter.PdfBody.PdfCrossReference addToObjStm(final PdfObject obj, final int nObj) throws IOException {
279            if (numObj >= OBJSINSTREAM)
280                flushObjStm();
281            if (index == null) {
282                index = new ByteBuffer();
283                streamObjects = new ByteBuffer();
284                currentObjNum = getIndirectReferenceNumber();
285                numObj = 0;
286            }
287            int p = streamObjects.size();
288            int idx = numObj++;
289            PdfEncryption enc = writer.crypto;
290            writer.crypto = null;
291            obj.toPdf(writer, streamObjects);
292            writer.crypto = enc;
293            streamObjects.append(' ');
294            index.append(nObj).append(' ').append(p).append(' ');
295            return new PdfWriter.PdfBody.PdfCrossReference(2, nObj, currentObjNum, idx);
296        }
297
298        private void flushObjStm() throws IOException {
299            if (numObj == 0)
300                return;
301            int first = index.size();
302            index.append(streamObjects);
303            PdfStream stream = new PdfStream(index.toByteArray());
304            stream.flateCompress(writer.getCompressionLevel());
305            stream.put(PdfName.TYPE, PdfName.OBJSTM);
306            stream.put(PdfName.N, new PdfNumber(numObj));
307            stream.put(PdfName.FIRST, new PdfNumber(first));
308            add(stream, currentObjNum);
309            index = null;
310            streamObjects = null;
311            numObj = 0;
312        }
313
314        /**
315         * Adds a <CODE>PdfObject</CODE> to the body.
316         * <P>
317         * This methods creates a <CODE>PdfIndirectObject</CODE> with a
318         * certain number, containing the given <CODE>PdfObject</CODE>.
319         * It also adds a <CODE>PdfCrossReference</CODE> for this object
320         * to an <CODE>ArrayList</CODE> that will be used to build the
321         * Cross-reference Table.
322         *
323         * @param               object                  a <CODE>PdfObject</CODE>
324         * @return              a <CODE>PdfIndirectObject</CODE>
325         * @throws IOException
326         */
327
328        PdfIndirectObject add(final PdfObject object) throws IOException {
329            return add(object, getIndirectReferenceNumber());
330        }
331
332        PdfIndirectObject add(final PdfObject object, final boolean inObjStm) throws IOException {
333            return add(object, getIndirectReferenceNumber(), inObjStm);
334        }
335
336        /**
337         * Gets a PdfIndirectReference for an object that will be created in the future.
338         * @return a PdfIndirectReference
339         */
340
341        PdfIndirectReference getPdfIndirectReference() {
342            return new PdfIndirectReference(0, getIndirectReferenceNumber());
343        }
344
345        int getIndirectReferenceNumber() {
346            int n = refnum++;
347            xrefs.add(new PdfCrossReference(n, 0, GENERATION_MAX));
348            return n;
349        }
350
351        /**
352         * Adds a <CODE>PdfObject</CODE> to the body given an already existing
353         * PdfIndirectReference.
354         * <P>
355         * This methods creates a <CODE>PdfIndirectObject</CODE> with the number given by
356         * <CODE>ref</CODE>, containing the given <CODE>PdfObject</CODE>.
357         * It also adds a <CODE>PdfCrossReference</CODE> for this object
358         * to an <CODE>ArrayList</CODE> that will be used to build the
359         * Cross-reference Table.
360         *
361         * @param               object                  a <CODE>PdfObject</CODE>
362         * @param               ref                     a <CODE>PdfIndirectReference</CODE>
363         * @return              a <CODE>PdfIndirectObject</CODE>
364         * @throws IOException
365         */
366
367        PdfIndirectObject add(final PdfObject object, final PdfIndirectReference ref) throws IOException {
368            return add(object, ref.getNumber());
369        }
370
371        PdfIndirectObject add(final PdfObject object, final PdfIndirectReference ref, final boolean inObjStm) throws IOException {
372            return add(object, ref.getNumber(), inObjStm);
373        }
374
375        PdfIndirectObject add(final PdfObject object, final int refNumber) throws IOException {
376            return add(object, refNumber, true); // to false
377        }
378
379        PdfIndirectObject add(final PdfObject object, final int refNumber, final boolean inObjStm) throws IOException {
380            if (inObjStm && object.canBeInObjStm() && writer.isFullCompression()) {
381                PdfCrossReference pxref = addToObjStm(object, refNumber);
382                PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer);
383                if (!xrefs.add(pxref)) {
384                    xrefs.remove(pxref);
385                    xrefs.add(pxref);
386                }
387                return indirect;
388            }
389            else {
390                PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer);
391                PdfCrossReference pxref = new PdfCrossReference(refNumber, position);
392                if (!xrefs.add(pxref)) {
393                    xrefs.remove(pxref);
394                    xrefs.add(pxref);
395                }
396                indirect.writeTo(writer.getOs());
397                position = writer.getOs().getCounter();
398                return indirect;
399            }
400        }
401
402        /**
403         * Returns the offset of the Cross-Reference table.
404         *
405         * @return              an offset
406         */
407
408        int offset() {
409            return position;
410        }
411
412        /**
413         * Returns the total number of objects contained in the CrossReferenceTable of this <CODE>Body</CODE>.
414         *
415         * @return      a number of objects
416         */
417
418        int size() {
419            return Math.max(xrefs.last().getRefnum() + 1, refnum);
420        }
421
422        /**
423         * Returns the CrossReferenceTable of the <CODE>Body</CODE>.
424         * @param os
425         * @param root
426         * @param info
427         * @param encryption
428         * @param fileID
429         * @param prevxref
430         * @throws IOException
431         */
432
433        void writeCrossReferenceTable(final OutputStream os, final PdfIndirectReference root, final PdfIndirectReference info, final PdfIndirectReference encryption, final PdfObject fileID, final int prevxref) throws IOException {
434            int refNumber = 0;
435            if (writer.isFullCompression()) {
436                flushObjStm();
437                refNumber = getIndirectReferenceNumber();
438                xrefs.add(new PdfCrossReference(refNumber, position));
439            }
440            PdfCrossReference entry = xrefs.first();
441            int first = entry.getRefnum();
442            int len = 0;
443            ArrayList<Integer> sections = new ArrayList<Integer>();
444            for (PdfCrossReference pdfCrossReference : xrefs) {
445                entry = pdfCrossReference;
446                if (first + len == entry.getRefnum())
447                    ++len;
448                else {
449                    sections.add(Integer.valueOf(first));
450                    sections.add(Integer.valueOf(len));
451                    first = entry.getRefnum();
452                    len = 1;
453                }
454            }
455            sections.add(Integer.valueOf(first));
456            sections.add(Integer.valueOf(len));
457            if (writer.isFullCompression()) {
458                int mid = 4;
459                int mask = 0xff000000;
460                for (; mid > 1; --mid) {
461                    if ((mask & position) != 0)
462                        break;
463                    mask >>>= 8;
464                }
465                ByteBuffer buf = new ByteBuffer();
466
467                for (Object element : xrefs) {
468                    entry = (PdfCrossReference) element;
469                    entry.toPdf(mid, buf);
470                }
471                PdfStream xr = new PdfStream(buf.toByteArray());
472                buf = null;
473                xr.flateCompress(writer.getCompressionLevel());
474                xr.put(PdfName.SIZE, new PdfNumber(size()));
475                xr.put(PdfName.ROOT, root);
476                if (info != null) {
477                    xr.put(PdfName.INFO, info);
478                }
479                if (encryption != null)
480                    xr.put(PdfName.ENCRYPT, encryption);
481                if (fileID != null)
482                    xr.put(PdfName.ID, fileID);
483                xr.put(PdfName.W, new PdfArray(new int[]{1, mid, 2}));
484                xr.put(PdfName.TYPE, PdfName.XREF);
485                PdfArray idx = new PdfArray();
486                for (int k = 0; k < sections.size(); ++k)
487                    idx.add(new PdfNumber(sections.get(k).intValue()));
488                xr.put(PdfName.INDEX, idx);
489                if (prevxref > 0)
490                    xr.put(PdfName.PREV, new PdfNumber(prevxref));
491                PdfEncryption enc = writer.crypto;
492                writer.crypto = null;
493                PdfIndirectObject indirect = new PdfIndirectObject(refNumber, xr, writer);
494                indirect.writeTo(writer.getOs());
495                writer.crypto = enc;
496            }
497            else {
498                os.write(getISOBytes("xref\n"));
499                Iterator<PdfCrossReference> i = xrefs.iterator();
500                for (int k = 0; k < sections.size(); k += 2) {
501                    first = sections.get(k).intValue();
502                    len = sections.get(k + 1).intValue();
503                    os.write(getISOBytes(String.valueOf(first)));
504                    os.write(getISOBytes(" "));
505                    os.write(getISOBytes(String.valueOf(len)));
506                    os.write('\n');
507                    while (len-- > 0) {
508                        entry = i.next();
509                        entry.toPdf(os);
510                    }
511                }
512            }
513        }
514    }
515
516    /**
517     * <CODE>PdfTrailer</CODE> is the PDF Trailer object.
518     * <P>
519     * This object is described in the 'Portable Document Format Reference Manual version 1.3'
520     * section 5.16 (page 59-60).
521     */
522
523    static class PdfTrailer extends PdfDictionary {
524
525        // membervariables
526
527        int offset;
528
529        // constructors
530
531        /**
532         * Constructs a PDF-Trailer.
533         *
534         * @param               size            the number of entries in the <CODE>PdfCrossReferenceTable</CODE>
535         * @param               offset          offset of the <CODE>PdfCrossReferenceTable</CODE>
536         * @param               root            an indirect reference to the root of the PDF document
537         * @param               info            an indirect reference to the info object of the PDF document
538         * @param encryption
539         * @param fileID
540         * @param prevxref
541         */
542
543        PdfTrailer(final int size, final int offset, final PdfIndirectReference root, final PdfIndirectReference info, final PdfIndirectReference encryption, final PdfObject fileID, final int prevxref) {
544            this.offset = offset;
545            put(PdfName.SIZE, new PdfNumber(size));
546            put(PdfName.ROOT, root);
547            if (info != null) {
548                put(PdfName.INFO, info);
549            }
550            if (encryption != null)
551                put(PdfName.ENCRYPT, encryption);
552            if (fileID != null)
553                put(PdfName.ID, fileID);
554            if (prevxref > 0)
555                put(PdfName.PREV, new PdfNumber(prevxref));
556        }
557
558        /**
559         * Returns the PDF representation of this <CODE>PdfObject</CODE>.
560         * @param writer
561         * @param os
562         * @throws IOException
563         */
564        @Override
565        public void toPdf(final PdfWriter writer, final OutputStream os) throws IOException {
566            os.write(getISOBytes("trailer\n"));
567            super.toPdf(null, os);
568            os.write(getISOBytes("\nstartxref\n"));
569            os.write(getISOBytes(String.valueOf(offset)));
570            os.write(getISOBytes("\n%%EOF\n"));
571        }
572    }
573
574//      ESSENTIALS
575
576//      Construct a PdfWriter instance
577
578    /**
579     * Constructs a <CODE>PdfWriter</CODE>.
580     */
581    protected PdfWriter() {
582    }
583
584    /**
585     * Constructs a <CODE>PdfWriter</CODE>.
586     * <P>
587     * Remark: a PdfWriter can only be constructed by calling the method
588     * <CODE>getInstance(Document document, OutputStream os)</CODE>.
589     *
590     * @param   document        The <CODE>PdfDocument</CODE> that has to be written
591     * @param   os                      The <CODE>OutputStream</CODE> the writer has to write to.
592     */
593
594    protected PdfWriter(final PdfDocument document, final OutputStream os) {
595        super(document, os);
596        pdf = document;
597        directContent = new PdfContentByte(this);
598        directContentUnder = new PdfContentByte(this);
599    }
600
601    /**
602     * Use this method to get an instance of the <CODE>PdfWriter</CODE>.
603     *
604     * @param   document        The <CODE>Document</CODE> that has to be written
605     * @param   os      The <CODE>OutputStream</CODE> the writer has to write to.
606     * @return  a new <CODE>PdfWriter</CODE>
607     *
608     * @throws  DocumentException on error
609     */
610
611    public static PdfWriter getInstance(final Document document, final OutputStream os)
612    throws DocumentException {
613        PdfDocument pdf = new PdfDocument();
614        document.addDocListener(pdf);
615        PdfWriter writer = new PdfWriter(pdf, os);
616        pdf.addWriter(writer);
617        return writer;
618    }
619
620    /**
621     * Use this method to get an instance of the <CODE>PdfWriter</CODE>.
622     *
623     * @return a new <CODE>PdfWriter</CODE>
624     * @param document The <CODE>Document</CODE> that has to be written
625     * @param os The <CODE>OutputStream</CODE> the writer has to write to.
626     * @param listener A <CODE>DocListener</CODE> to pass to the PdfDocument.
627     * @throws DocumentException on error
628     */
629
630    public static PdfWriter getInstance(final Document document, final OutputStream os, final DocListener listener)
631    throws DocumentException {
632        PdfDocument pdf = new PdfDocument();
633        pdf.addDocListener(listener);
634        document.addDocListener(pdf);
635        PdfWriter writer = new PdfWriter(pdf, os);
636        pdf.addWriter(writer);
637        return writer;
638    }
639
640//      the PdfDocument instance
641
642    /** the pdfdocument object. */
643    protected PdfDocument pdf;
644
645    /**
646     * Gets the <CODE>PdfDocument</CODE> associated with this writer.
647     * @return the <CODE>PdfDocument</CODE>
648     */
649
650    PdfDocument getPdfDocument() {
651        return pdf;
652    }
653
654    /**
655     * Use this method to get the info dictionary if you want to
656     * change it directly (add keys and values to the info dictionary).
657     * @return the info dictionary
658     */
659    public PdfDictionary getInfo() {
660        return pdf.getInfo();
661    }
662
663    /**
664     * Use this method to get the current vertical page position.
665     * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects
666     *   for elements that do not terminate the lines they've started because those lines will get
667     *   terminated.
668     * @return The current vertical page position.
669     */
670    public float getVerticalPosition(final boolean ensureNewLine) {
671        return pdf.getVerticalPosition(ensureNewLine);
672    }
673
674    /**
675     * Sets the initial leading for the PDF document.
676     * This has to be done before the document is opened.
677     * @param   leading the initial leading
678     * @since   2.1.6
679     * @throws  DocumentException       if you try setting the leading after the document was opened.
680     */
681    public void setInitialLeading(final float leading) throws DocumentException {
682        if (open)
683                throw new DocumentException(MessageLocalization.getComposedMessage("you.can.t.set.the.initial.leading.if.the.document.is.already.open"));
684        pdf.setLeading(leading);
685    }
686
687//      the PdfDirectContentByte instances
688
689/*
690 * You should see Direct Content as a canvas on which you can draw
691 * graphics and text. One canvas goes on top of the page (getDirectContent),
692 * the other goes underneath (getDirectContentUnder).
693 * You can always the same object throughout your document,
694 * even if you have moved to a new page. Whatever you add on
695 * the canvas will be displayed on top or under the current page.
696 */
697
698    /** The direct content in this document. */
699    protected PdfContentByte directContent;
700
701    /** The direct content under in this document. */
702    protected PdfContentByte directContentUnder;
703
704    /**
705     * Use this method to get the direct content for this document.
706     * There is only one direct content, multiple calls to this method
707     * will allways retrieve the same object.
708     * @return the direct content
709     */
710
711    public PdfContentByte getDirectContent() {
712        if (!open)
713            throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
714        return directContent;
715    }
716
717    /**
718     * Use this method to get the direct content under for this document.
719     * There is only one direct content, multiple calls to this method
720     * will always retrieve the same object.
721     * @return the direct content
722     */
723
724    public PdfContentByte getDirectContentUnder() {
725        if (!open)
726            throw new RuntimeException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
727        return directContentUnder;
728    }
729
730    /**
731     * Resets all the direct contents to empty.
732     * This happens when a new page is started.
733     */
734    void resetContent() {
735        directContent.reset();
736        directContentUnder.reset();
737    }
738
739//      PDF body
740
741/*
742 * A PDF file has 4 parts: a header, a body, a cross-reference table, and a trailer.
743 * The body contains all the PDF objects that make up the PDF document.
744 * Each element gets a reference (a set of numbers) and the byte position of
745 * every object is stored in the cross-reference table.
746 * Use these methods only if you know what you're doing.
747 */
748
749    /** body of the PDF document */
750    protected PdfBody body;
751
752    /**
753     * Adds the local destinations to the body of the document.
754     * @param desto the <CODE>HashMap</CODE> containing the destinations
755     * @throws IOException on error
756     */
757
758    void addLocalDestinations(final TreeMap<String, PdfDocument.Destination> desto) throws IOException {
759        for (Map.Entry<String, PdfDocument.Destination> entry : desto.entrySet()) {
760            String name = entry.getKey();
761            PdfDocument.Destination dest = entry.getValue();
762            PdfDestination destination = dest.destination;
763            if (dest.reference == null)
764                dest.reference = getPdfIndirectReference();
765            if (destination == null)
766                addToBody(new PdfString("invalid_" + name), dest.reference);
767            else
768                addToBody(destination, dest.reference);
769        }
770    }
771
772    /**
773     * Use this method to add a PDF object to the PDF body.
774     * Use this method only if you know what you're doing!
775     * @param object
776     * @return a PdfIndirectObject
777     * @throws IOException
778     */
779    public PdfIndirectObject addToBody(final PdfObject object) throws IOException {
780        PdfIndirectObject iobj = body.add(object);
781        return iobj;
782    }
783
784    /**
785     * Use this method to add a PDF object to the PDF body.
786     * Use this method only if you know what you're doing!
787     * @param object
788     * @param inObjStm
789     * @return a PdfIndirectObject
790     * @throws IOException
791     */
792    public PdfIndirectObject addToBody(final PdfObject object, final boolean inObjStm) throws IOException {
793        PdfIndirectObject iobj = body.add(object, inObjStm);
794        return iobj;
795    }
796
797    /**
798     * Use this method to add a PDF object to the PDF body.
799     * Use this method only if you know what you're doing!
800     * @param object
801     * @param ref
802     * @return a PdfIndirectObject
803     * @throws IOException
804     */
805    public PdfIndirectObject addToBody(final PdfObject object, final PdfIndirectReference ref) throws IOException {
806        PdfIndirectObject iobj = body.add(object, ref);
807        return iobj;
808    }
809
810    /**
811     * Use this method to add a PDF object to the PDF body.
812     * Use this method only if you know what you're doing!
813     * @param object
814     * @param ref
815     * @param inObjStm
816     * @return a PdfIndirectObject
817     * @throws IOException
818     */
819    public PdfIndirectObject addToBody(final PdfObject object, final PdfIndirectReference ref, final boolean inObjStm) throws IOException {
820        PdfIndirectObject iobj = body.add(object, ref, inObjStm);
821        return iobj;
822    }
823
824    /**
825     * Use this method to add a PDF object to the PDF body.
826     * Use this method only if you know what you're doing!
827     * @param object
828     * @param refNumber
829     * @return a PdfIndirectObject
830     * @throws IOException
831     */
832    public PdfIndirectObject addToBody(final PdfObject object, final int refNumber) throws IOException {
833        PdfIndirectObject iobj = body.add(object, refNumber);
834        return iobj;
835    }
836
837    /**
838     * Use this method to add a PDF object to the PDF body.
839     * Use this method only if you know what you're doing!
840     * @param object
841     * @param refNumber
842     * @param inObjStm
843     * @return a PdfIndirectObject
844     * @throws IOException
845     */
846    public PdfIndirectObject addToBody(final PdfObject object, final int refNumber, final boolean inObjStm) throws IOException {
847        PdfIndirectObject iobj = body.add(object, refNumber, inObjStm);
848        return iobj;
849    }
850
851    /**
852     * Use this to get an <CODE>PdfIndirectReference</CODE> for an object that
853     * will be created in the future.
854     * Use this method only if you know what you're doing!
855     * @return the <CODE>PdfIndirectReference</CODE>
856     */
857
858    public PdfIndirectReference getPdfIndirectReference() {
859        return body.getPdfIndirectReference();
860    }
861
862    int getIndirectReferenceNumber() {
863        return body.getIndirectReferenceNumber();
864    }
865
866    /**
867     * Returns the outputStreamCounter.
868     * @return the outputStreamCounter
869     */
870    OutputStreamCounter getOs() {
871        return os;
872    }
873
874
875//      PDF Catalog
876
877/*
878 * The Catalog is also called the root object of the document.
879 * Whereas the Cross-Reference maps the objects number with the
880 * byte offset so that the viewer can find the objects, the
881 * Catalog tells the viewer the numbers of the objects needed
882 * to render the document.
883 */
884
885    protected PdfDictionary getCatalog(final PdfIndirectReference rootObj)
886    {
887        PdfDictionary catalog = pdf.getCatalog(rootObj);
888        // [F12] tagged PDF
889        if (tagged) {
890            try {
891                getStructureTreeRoot().buildTree();
892            }
893            catch (Exception e) {
894                throw new ExceptionConverter(e);
895            }
896            catalog.put(PdfName.STRUCTTREEROOT, structureTreeRoot.getReference());
897            PdfDictionary mi = new PdfDictionary();
898            mi.put(PdfName.MARKED, PdfBoolean.PDFTRUE);
899            if (userProperties)
900                mi.put(PdfName.USERPROPERTIES, PdfBoolean.PDFTRUE);
901            catalog.put(PdfName.MARKINFO, mi);
902        }
903        // [F13] OCG
904        if (!documentOCG.isEmpty()) {
905                fillOCProperties(false);
906                catalog.put(PdfName.OCPROPERTIES, OCProperties);
907        }
908        return catalog;
909    }
910
911    /** Holds value of property extraCatalog this is used for Output Intents. */
912    protected PdfDictionary extraCatalog;
913
914    /**
915     * Sets extra keys to the catalog.
916     * @return the catalog to change
917     */
918    public PdfDictionary getExtraCatalog() {
919        if (extraCatalog == null)
920            extraCatalog = new PdfDictionary();
921        return this.extraCatalog;
922    }
923
924//      PdfPages
925
926/*
927 * The page root keeps the complete page tree of the document.
928 * There's an entry in the Catalog that refers to the root
929 * of the page tree, the page tree contains the references
930 * to pages and other page trees.
931 */
932
933    /** The root of the page tree. */
934    protected PdfPages root = new PdfPages(this);
935    /** The PdfIndirectReference to the pages. */
936    protected ArrayList<PdfIndirectReference> pageReferences = new ArrayList<PdfIndirectReference>();
937    /** The current page number. */
938    protected int currentPageNumber = 1;
939    /**
940     * The value of the Tabs entry in the page dictionary.
941     * @since   2.1.5
942     */
943    protected PdfName tabs = null;
944
945    /**
946     * Additional page dictionary entries.
947     * @since 5.1.0
948     */
949    protected PdfDictionary pageDictEntries = new PdfDictionary();
950
951    /**
952     * Adds an additional entry for the page dictionary.
953     * @param key the key
954     * @param object the PdfObject for the given key
955     * @since 5.1.0
956     */
957    public void addPageDictEntry(final PdfName key, final PdfObject object) {
958        pageDictEntries.put(key, object);
959    }
960
961    /**
962     * Gets the additional pageDictEntries.
963     * @return the page dictionary entries
964     * @since 5.1.0
965     */
966    public PdfDictionary getPageDictEntries() {
967        return pageDictEntries;
968    }
969
970    /**
971     * Resets the additional pageDictEntries.
972     * @since 5.1.0
973     */
974    public void resetPageDictEntries() {
975        pageDictEntries = new PdfDictionary();
976    }
977
978    /**
979     * Use this method to make sure the page tree has a linear structure
980     * (every leave is attached directly to the root).
981     * Use this method to allow page reordering with method reorderPages.
982     */
983     public void setLinearPageMode() {
984        root.setLinearMode(null);
985    }
986
987    /**
988     * Use this method to reorder the pages in the document.
989     * A <CODE>null</CODE> argument value only returns the number of pages to process.
990     * It is advisable to issue a <CODE>Document.newPage()</CODE> before using this method.
991     * @return the total number of pages
992     * @param order an array with the new page sequence. It must have the
993     * same size as the number of pages.
994     * @throws DocumentException if all the pages are not present in the array
995     */
996    public int reorderPages(final int order[]) throws DocumentException {
997        return root.reorderPages(order);
998    }
999
1000    /**
1001     * Use this method to get a reference to a page existing or not.
1002     * If the page does not exist yet the reference will be created
1003     * in advance. If on closing the document, a page number greater
1004     * than the total number of pages was requested, an exception
1005     * is thrown.
1006     * @param page the page number. The first page is 1
1007     * @return the reference to the page
1008     */
1009    public PdfIndirectReference getPageReference(int page) {
1010        --page;
1011        if (page < 0)
1012            throw new IndexOutOfBoundsException(MessageLocalization.getComposedMessage("the.page.number.must.be.gt.eq.1"));
1013        PdfIndirectReference ref;
1014        if (page < pageReferences.size()) {
1015            ref = pageReferences.get(page);
1016            if (ref == null) {
1017                ref = body.getPdfIndirectReference();
1018                pageReferences.set(page, ref);
1019            }
1020        }
1021        else {
1022            int empty = page - pageReferences.size();
1023            for (int k = 0; k < empty; ++k)
1024                pageReferences.add(null);
1025            ref = body.getPdfIndirectReference();
1026            pageReferences.add(ref);
1027        }
1028        return ref;
1029    }
1030
1031    /**
1032     * Gets the pagenumber of this document.
1033     * This number can be different from the real pagenumber,
1034     * if you have (re)set the page number previously.
1035     * @return a page number
1036     */
1037
1038    public int getPageNumber() {
1039        return pdf.getPageNumber();
1040    }
1041
1042    PdfIndirectReference getCurrentPage() {
1043        return getPageReference(currentPageNumber);
1044    }
1045
1046    public int getCurrentPageNumber() {
1047        return currentPageNumber;
1048    }
1049
1050    /**
1051     * Sets the Viewport for the next page.
1052     * @param vp an array consisting of Viewport dictionaries.
1053     * @since 5.1.0
1054     */
1055    public void setPageViewport(final PdfArray vp) {
1056        addPageDictEntry(PdfName.VP, vp);
1057    }
1058
1059    /**
1060     * Sets the value for the Tabs entry in the page tree.
1061     * @param   tabs    Can be PdfName.R, PdfName.C or PdfName.S.
1062     * Since the Adobe Extensions Level 3, it can also be PdfName.A
1063     * or PdfName.W
1064     * @since   2.1.5
1065     */
1066    public void setTabs(final PdfName tabs) {
1067        this.tabs = tabs;
1068    }
1069
1070    /**
1071     * Returns the value to be used for the Tabs entry in the page tree.
1072     * @return the Tabs PdfName
1073     * @since   2.1.5
1074     */
1075    public PdfName getTabs() {
1076        return tabs;
1077    }
1078
1079    /**
1080     * Adds some <CODE>PdfContents</CODE> to this Writer.
1081     * <P>
1082     * The document has to be open before you can begin to add content
1083     * to the body of the document.
1084     *
1085     * @return a <CODE>PdfIndirectReference</CODE>
1086     * @param page the <CODE>PdfPage</CODE> to add
1087     * @param contents the <CODE>PdfContents</CODE> of the page
1088     * @throws PdfException on error
1089     */
1090
1091    PdfIndirectReference add(final PdfPage page, final PdfContents contents) throws PdfException {
1092        if (!open) {
1093            throw new PdfException(MessageLocalization.getComposedMessage("the.document.is.not.open"));
1094        }
1095        PdfIndirectObject object;
1096        try {
1097            object = addToBody(contents);
1098        }
1099        catch(IOException ioe) {
1100            throw new ExceptionConverter(ioe);
1101        }
1102        page.add(object.getIndirectReference());
1103        // [U5]
1104        if (group != null) {
1105            page.put(PdfName.GROUP, group);
1106            group = null;
1107        }
1108        else if (rgbTransparencyBlending) {
1109            PdfDictionary pp = new PdfDictionary();
1110            pp.put(PdfName.TYPE, PdfName.GROUP);
1111            pp.put(PdfName.S, PdfName.TRANSPARENCY);
1112            pp.put(PdfName.CS, PdfName.DEVICERGB);
1113            page.put(PdfName.GROUP, pp);
1114        }
1115        root.addPage(page);
1116        currentPageNumber++;
1117        return null;
1118    }
1119
1120//      page events
1121
1122/*
1123 * Page events are specific for iText, not for PDF.
1124 * Upon specific events (for instance when a page starts
1125 * or ends), the corresponding method in the page event
1126 * implementation that is added to the writer is invoked.
1127 */
1128
1129    /** The <CODE>PdfPageEvent</CODE> for this document. */
1130    private PdfPageEvent pageEvent;
1131
1132    /**
1133     * Sets the <CODE>PdfPageEvent</CODE> for this document.
1134     * @param event the <CODE>PdfPageEvent</CODE> for this document
1135     */
1136
1137    public void setPageEvent(final PdfPageEvent event) {
1138        if (event == null) this.pageEvent = null;
1139        else if (this.pageEvent == null) this.pageEvent = event;
1140        else if (this.pageEvent instanceof PdfPageEventForwarder) ((PdfPageEventForwarder)this.pageEvent).addPageEvent(event);
1141        else {
1142                PdfPageEventForwarder forward = new PdfPageEventForwarder();
1143                forward.addPageEvent(this.pageEvent);
1144                forward.addPageEvent(event);
1145                this.pageEvent = forward;
1146        }
1147    }
1148
1149    /**
1150     * Gets the <CODE>PdfPageEvent</CODE> for this document or <CODE>null</CODE>
1151     * if none is set.
1152     * @return the <CODE>PdfPageEvent</CODE> for this document or <CODE>null</CODE>
1153     * if none is set
1154     */
1155
1156    public PdfPageEvent getPageEvent() {
1157        return pageEvent;
1158    }
1159
1160//      Open and Close methods + method that create the PDF
1161
1162    /** A number referring to the previous Cross-Reference Table. */
1163    protected int prevxref = 0;
1164
1165    /**
1166     * Signals that the <CODE>Document</CODE> has been opened and that
1167     * <CODE>Elements</CODE> can be added.
1168     * <P>
1169     * When this method is called, the PDF-document header is
1170     * written to the outputstream.
1171     * @see com.itextpdf.text.DocWriter#open()
1172     */
1173    @Override
1174    public void open() {
1175        super.open();
1176        try {
1177                pdf_version.writeHeader(os);
1178            body = new PdfBody(this);
1179            if (pdfxConformance.isPdfX32002()) {
1180                PdfDictionary sec = new PdfDictionary();
1181                sec.put(PdfName.GAMMA, new PdfArray(new float[]{2.2f,2.2f,2.2f}));
1182                sec.put(PdfName.MATRIX, new PdfArray(new float[]{0.4124f,0.2126f,0.0193f,0.3576f,0.7152f,0.1192f,0.1805f,0.0722f,0.9505f}));
1183                sec.put(PdfName.WHITEPOINT, new PdfArray(new float[]{0.9505f,1f,1.089f}));
1184                PdfArray arr = new PdfArray(PdfName.CALRGB);
1185                arr.add(sec);
1186                setDefaultColorspace(PdfName.DEFAULTRGB, addToBody(arr).getIndirectReference());
1187            }
1188        }
1189        catch(IOException ioe) {
1190            throw new ExceptionConverter(ioe);
1191        }
1192    }
1193
1194    /**
1195     * Signals that the <CODE>Document</CODE> was closed and that no other
1196     * <CODE>Elements</CODE> will be added.
1197     * <P>
1198     * The pages-tree is built and written to the outputstream.
1199     * A Catalog is constructed, as well as an Info-object,
1200     * the reference table is composed and everything is written
1201     * to the outputstream embedded in a Trailer.
1202     * @see com.itextpdf.text.DocWriter#close()
1203     */
1204    @Override
1205    public void close() {
1206        if (open) {
1207            if (currentPageNumber - 1 != pageReferences.size())
1208                throw new RuntimeException("The page " + pageReferences.size() +
1209                " was requested but the document has only " + (currentPageNumber - 1) + " pages.");
1210            pdf.close();
1211            try {
1212                addSharedObjectsToBody();
1213                for (PdfOCG layer : documentOCG) {
1214                    addToBody(layer.getPdfObject(), layer.getRef());
1215                }
1216                // add the root to the body
1217                PdfIndirectReference rootRef = root.writePageTree();
1218                // make the catalog-object and add it to the body
1219                PdfDictionary catalog = getCatalog(rootRef);
1220                // [C9] if there is XMP data to add: add it
1221                if (xmpMetadata != null) {
1222                        PdfStream xmp = new PdfStream(xmpMetadata);
1223                        xmp.put(PdfName.TYPE, PdfName.METADATA);
1224                        xmp.put(PdfName.SUBTYPE, PdfName.XML);
1225                    if (crypto != null && !crypto.isMetadataEncrypted()) {
1226                        PdfArray ar = new PdfArray();
1227                        ar.add(PdfName.CRYPT);
1228                        xmp.put(PdfName.FILTER, ar);
1229                    }
1230                        catalog.put(PdfName.METADATA, body.add(xmp).getIndirectReference());
1231                }
1232                // [C10] make pdfx conformant
1233                if (isPdfX()) {
1234                    pdfxConformance.completeInfoDictionary(getInfo());
1235                    pdfxConformance.completeExtraCatalog(getExtraCatalog());
1236                }
1237                // [C11] Output Intents
1238                if (extraCatalog != null) {
1239                    catalog.mergeDifferent(extraCatalog);
1240                }
1241
1242                writeOutlines(catalog, false);
1243
1244                // add the Catalog to the body
1245                PdfIndirectObject indirectCatalog = addToBody(catalog, false);
1246                // add the info-object to the body
1247                PdfIndirectObject infoObj = addToBody(getInfo(), false);
1248
1249                // [F1] encryption
1250                PdfIndirectReference encryption = null;
1251                PdfObject fileID = null;
1252                body.flushObjStm();
1253                if (crypto != null) {
1254                    PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false);
1255                    encryption = encryptionObject.getIndirectReference();
1256                    fileID = crypto.getFileID();
1257                }
1258                else
1259                    fileID = PdfEncryption.createInfoId(PdfEncryption.createDocumentId());
1260
1261                // write the cross-reference table of the body
1262                body.writeCrossReferenceTable(os, indirectCatalog.getIndirectReference(),
1263                    infoObj.getIndirectReference(), encryption,  fileID, prevxref);
1264
1265                // make the trailer
1266                // [F2] full compression
1267                if (fullCompression) {
1268                    os.write(getISOBytes("startxref\n"));
1269                    os.write(getISOBytes(String.valueOf(body.offset())));
1270                    os.write(getISOBytes("\n%%EOF\n"));
1271                }
1272                else {
1273                    PdfTrailer trailer = new PdfTrailer(body.size(),
1274                    body.offset(),
1275                    indirectCatalog.getIndirectReference(),
1276                    infoObj.getIndirectReference(),
1277                    encryption,
1278                    fileID, prevxref);
1279                    trailer.toPdf(this, os);
1280                }
1281                super.close();
1282            }
1283            catch(IOException ioe) {
1284                throw new ExceptionConverter(ioe);
1285            }
1286        }
1287    }
1288
1289    protected void addSharedObjectsToBody() throws IOException {
1290        // [F3] add the fonts
1291        for (FontDetails details : documentFonts.values()) {
1292            details.writeFont(this);
1293        }
1294        // [F4] add the form XObjects
1295        for (Object objs[] : formXObjects.values()) {
1296            PdfTemplate template = (PdfTemplate)objs[1];
1297            if (template != null && template.getIndirectReference() instanceof PRIndirectReference)
1298                continue;
1299            if (template != null && template.getType() == PdfTemplate.TYPE_TEMPLATE) {
1300                addToBody(template.getFormXObject(compressionLevel), template.getIndirectReference());
1301            }
1302        }
1303        // [F5] add all the dependencies in the imported pages
1304        for (PdfReaderInstance element : readerInstances.values()) {
1305            currentPdfReaderInstance= element;
1306            currentPdfReaderInstance.writeAllPages();
1307        }
1308        currentPdfReaderInstance = null;
1309        // [F6] add the spotcolors
1310        for (ColorDetails color : documentColors.values()) {
1311            addToBody(color.getSpotColor(this), color.getIndirectReference());
1312        }
1313        // [F7] add the pattern
1314        for (PdfPatternPainter pat : documentPatterns.keySet()) {
1315            addToBody(pat.getPattern(compressionLevel), pat.getIndirectReference());
1316        }
1317        // [F8] add the shading patterns
1318        for (PdfShadingPattern shadingPattern : documentShadingPatterns) {
1319            shadingPattern.addToBody();
1320        }
1321        // [F9] add the shadings
1322        for (PdfShading shading : documentShadings) {
1323            shading.addToBody();
1324        }
1325        // [F10] add the extgstate
1326        for (Map.Entry<PdfDictionary, PdfObject[]>entry : documentExtGState.entrySet()) {
1327            PdfDictionary gstate = entry.getKey();
1328            PdfObject obj[] = entry.getValue();
1329            addToBody(gstate, (PdfIndirectReference)obj[1]);
1330        }
1331        // [F11] add the properties
1332        for (Map.Entry<Object, PdfObject[]>entry : documentProperties.entrySet()) {
1333            Object prop = entry.getKey();
1334            PdfObject[] obj = entry.getValue();
1335            if (prop instanceof PdfLayerMembership){
1336                PdfLayerMembership layer = (PdfLayerMembership)prop;
1337                addToBody(layer.getPdfObject(), layer.getRef());
1338            }
1339            else if (prop instanceof PdfDictionary && !(prop instanceof PdfLayer)){
1340                addToBody((PdfDictionary)prop, (PdfIndirectReference)obj[1]);
1341            }
1342        }
1343    }
1344
1345// Root data for the PDF document (used when composing the Catalog)
1346
1347//  [C1] Outlines (bookmarks)
1348
1349     /**
1350      * Use this method to get the root outline
1351      * and construct bookmarks.
1352      * @return the root outline
1353      */
1354
1355     public PdfOutline getRootOutline() {
1356         return directContent.getRootOutline();
1357     }
1358
1359     protected List<HashMap<String, Object>> newBookmarks;
1360
1361    /**
1362     * Sets the bookmarks. The list structure is defined in
1363     * {@link SimpleBookmark}.
1364     * @param outlines the bookmarks or <CODE>null</CODE> to remove any
1365     */
1366    public void setOutlines(final List<HashMap<String, Object>> outlines) {
1367        newBookmarks = outlines;
1368    }
1369
1370    protected void writeOutlines(final PdfDictionary catalog, final boolean namedAsNames) throws IOException {
1371        if (newBookmarks == null || newBookmarks.isEmpty())
1372            return;
1373        PdfDictionary top = new PdfDictionary();
1374        PdfIndirectReference topRef = getPdfIndirectReference();
1375        Object kids[] = SimpleBookmark.iterateOutlines(this, topRef, newBookmarks, namedAsNames);
1376        top.put(PdfName.FIRST, (PdfIndirectReference)kids[0]);
1377        top.put(PdfName.LAST, (PdfIndirectReference)kids[1]);
1378        top.put(PdfName.COUNT, new PdfNumber(((Integer)kids[2]).intValue()));
1379        addToBody(top, topRef);
1380        catalog.put(PdfName.OUTLINES, topRef);
1381    }
1382
1383//      [C2] PdfVersion interface
1384     /** possible PDF version (header) */
1385     public static final char VERSION_1_2 = '2';
1386     /** possible PDF version (header) */
1387     public static final char VERSION_1_3 = '3';
1388     /** possible PDF version (header) */
1389     public static final char VERSION_1_4 = '4';
1390     /** possible PDF version (header) */
1391     public static final char VERSION_1_5 = '5';
1392     /** possible PDF version (header) */
1393     public static final char VERSION_1_6 = '6';
1394     /** possible PDF version (header) */
1395     public static final char VERSION_1_7 = '7';
1396
1397     /** possible PDF version (catalog) */
1398     public static final PdfName PDF_VERSION_1_2 = new PdfName("1.2");
1399     /** possible PDF version (catalog) */
1400     public static final PdfName PDF_VERSION_1_3 = new PdfName("1.3");
1401     /** possible PDF version (catalog) */
1402     public static final PdfName PDF_VERSION_1_4 = new PdfName("1.4");
1403     /** possible PDF version (catalog) */
1404     public static final PdfName PDF_VERSION_1_5 = new PdfName("1.5");
1405     /** possible PDF version (catalog) */
1406     public static final PdfName PDF_VERSION_1_6 = new PdfName("1.6");
1407     /** possible PDF version (catalog) */
1408     public static final PdfName PDF_VERSION_1_7 = new PdfName("1.7");
1409
1410    /** Stores the version information for the header and the catalog. */
1411    protected PdfVersionImp pdf_version = new PdfVersionImp();
1412
1413    /** @see com.itextpdf.text.pdf.interfaces.PdfVersion#setPdfVersion(char) */
1414    public void setPdfVersion(final char version) {
1415        pdf_version.setPdfVersion(version);
1416    }
1417
1418    /** @see com.itextpdf.text.pdf.interfaces.PdfVersion#setAtLeastPdfVersion(char) */
1419    public void setAtLeastPdfVersion(final char version) {
1420        pdf_version.setAtLeastPdfVersion(version);
1421    }
1422
1423        /** @see com.itextpdf.text.pdf.interfaces.PdfVersion#setPdfVersion(com.itextpdf.text.pdf.PdfName) */
1424        public void setPdfVersion(final PdfName version) {
1425                pdf_version.setPdfVersion(version);
1426        }
1427
1428        /**
1429         * @see com.itextpdf.text.pdf.interfaces.PdfVersion#addDeveloperExtension(com.itextpdf.text.pdf.PdfDeveloperExtension)
1430         * @since       2.1.6
1431         */
1432        public void addDeveloperExtension(final PdfDeveloperExtension de) {
1433                pdf_version.addDeveloperExtension(de);
1434        }
1435
1436        /**
1437         * Returns the version information.
1438         * @return the PdfVersion
1439         */
1440        PdfVersionImp getPdfVersion() {
1441                return pdf_version;
1442        }
1443
1444//  [C3] PdfViewerPreferences interface
1445
1446        // page layout (section 13.1.1 of "iText in Action")
1447
1448        /** A viewer preference */
1449        public static final int PageLayoutSinglePage = 1;
1450        /** A viewer preference */
1451        public static final int PageLayoutOneColumn = 2;
1452        /** A viewer preference */
1453        public static final int PageLayoutTwoColumnLeft = 4;
1454        /** A viewer preference */
1455        public static final int PageLayoutTwoColumnRight = 8;
1456        /** A viewer preference */
1457        public static final int PageLayoutTwoPageLeft = 16;
1458        /** A viewer preference */
1459        public static final int PageLayoutTwoPageRight = 32;
1460
1461    // page mode (section 13.1.2 of "iText in Action")
1462
1463    /** A viewer preference */
1464    public static final int PageModeUseNone = 64;
1465    /** A viewer preference */
1466    public static final int PageModeUseOutlines = 128;
1467    /** A viewer preference */
1468    public static final int PageModeUseThumbs = 256;
1469    /** A viewer preference */
1470    public static final int PageModeFullScreen = 512;
1471    /** A viewer preference */
1472    public static final int PageModeUseOC = 1024;
1473    /** A viewer preference */
1474    public static final int PageModeUseAttachments = 2048;
1475
1476    // values for setting viewer preferences in iText versions older than 2.x
1477
1478    /** A viewer preference */
1479    public static final int HideToolbar = 1 << 12;
1480    /** A viewer preference */
1481    public static final int HideMenubar = 1 << 13;
1482    /** A viewer preference */
1483    public static final int HideWindowUI = 1 << 14;
1484    /** A viewer preference */
1485    public static final int FitWindow = 1 << 15;
1486    /** A viewer preference */
1487    public static final int CenterWindow = 1 << 16;
1488    /** A viewer preference */
1489    public static final int DisplayDocTitle = 1 << 17;
1490
1491    /** A viewer preference */
1492    public static final int NonFullScreenPageModeUseNone = 1 << 18;
1493    /** A viewer preference */
1494    public static final int NonFullScreenPageModeUseOutlines = 1 << 19;
1495    /** A viewer preference */
1496    public static final int NonFullScreenPageModeUseThumbs = 1 << 20;
1497    /** A viewer preference */
1498    public static final int NonFullScreenPageModeUseOC = 1 << 21;
1499
1500    /** A viewer preference */
1501    public static final int DirectionL2R = 1 << 22;
1502    /** A viewer preference */
1503    public static final int DirectionR2L = 1 << 23;
1504
1505    /** A viewer preference */
1506    public static final int PrintScalingNone = 1 << 24;
1507
1508    /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */
1509    public void setViewerPreferences(final int preferences) {
1510        pdf.setViewerPreferences(preferences);
1511    }
1512
1513    /** @see com.itextpdf.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfObject) */
1514    public void addViewerPreference(final PdfName key, final PdfObject value) {
1515        pdf.addViewerPreference(key, value);
1516    }
1517
1518//  [C4] Page labels
1519
1520    /**
1521     * Use this method to add page labels
1522     * @param pageLabels the page labels
1523     */
1524    public void setPageLabels(final PdfPageLabels pageLabels) {
1525        pdf.setPageLabels(pageLabels);
1526    }
1527
1528//  [C5] named objects: named destinations, javascript, embedded files
1529
1530    /**
1531     * Adds named destinations in bulk.
1532     * Valid keys and values of the map can be found in the map
1533     * that is created by SimpleNamedDestination.
1534     * @param   map     a map with strings as keys for the names,
1535     *                  and structured strings as values for the destinations
1536     * @param   page_offset     number of pages that has to be added to
1537     *                  the page numbers in the destinations (useful if you
1538     *          use this method in combination with PdfCopy).
1539     * @since   iText 5.0
1540     */
1541    public void addNamedDestinations(final Map<String, String> map, final int page_offset) {
1542        int page;
1543        String dest;
1544        PdfDestination destination;
1545        for (Map.Entry<String, String> entry : map.entrySet()) {
1546                dest = entry.getValue();
1547                page = Integer.parseInt(dest.substring(0, dest.indexOf(" ")));
1548                destination = new PdfDestination(dest.substring(dest.indexOf(" ") + 1));
1549                addNamedDestination(entry.getKey(), page + page_offset, destination);
1550        }
1551    }
1552
1553    /**
1554     * Adds one named destination.
1555     * @param   name    the name for the destination
1556     * @param   page    the page number where you want to jump to
1557     * @param   dest    an explicit destination
1558     * @since   iText 5.0
1559     */
1560    public void addNamedDestination(final String name, final int page, final PdfDestination dest) {
1561        dest.addPage(getPageReference(page));
1562        pdf.localDestination(name, dest);
1563    }
1564
1565     /**
1566      * Use this method to add a JavaScript action at the document level.
1567      * When the document opens, all this JavaScript runs.
1568      * @param js The JavaScript action
1569      */
1570     public void addJavaScript(final PdfAction js) {
1571         pdf.addJavaScript(js);
1572     }
1573
1574     /**
1575      * Use this method to add a JavaScript action at the document level.
1576      * When the document opens, all this JavaScript runs.
1577      * @param code the JavaScript code
1578      * @param unicode select JavaScript unicode. Note that the internal
1579      * Acrobat JavaScript engine does not support unicode,
1580      * so this may or may not work for you
1581      */
1582     public void addJavaScript(final String code, final boolean unicode) {
1583         addJavaScript(PdfAction.javaScript(code, this, unicode));
1584     }
1585
1586     /**
1587      * Use this method to adds a JavaScript action at the document level.
1588      * When the document opens, all this JavaScript runs.
1589      * @param code the JavaScript code
1590      */
1591     public void addJavaScript(final String code) {
1592         addJavaScript(code, false);
1593     }
1594     /**
1595      * Use this method to add a JavaScript action at the document level.
1596      * When the document opens, all this JavaScript runs.
1597      * @param name     The name of the JS Action in the name tree
1598      * @param js The JavaScript action
1599      */
1600     public void addJavaScript(final String name, final PdfAction js) {
1601         pdf.addJavaScript(name, js);
1602     }
1603
1604     /**
1605      * Use this method to add a JavaScript action at the document level.
1606      * When the document opens, all this JavaScript runs.
1607      * @param name     The name of the JS Action in the name tree
1608      * @param code the JavaScript code
1609      * @param unicode select JavaScript unicode. Note that the internal
1610      * Acrobat JavaScript engine does not support unicode,
1611      * so this may or may not work for you
1612      */
1613     public void addJavaScript(final String name, final String code, final boolean unicode) {
1614         addJavaScript(name, PdfAction.javaScript(code, this, unicode));
1615     }
1616
1617     /**
1618      * Use this method to adds a JavaScript action at the document level.
1619      * When the document opens, all this JavaScript runs.
1620      * @param name     The name of the JS Action in the name tree
1621      * @param code the JavaScript code
1622      */
1623     public void addJavaScript(final String name, final String code) {
1624         addJavaScript(name, code, false);
1625     }
1626
1627     /**
1628      * Use this method to add a file attachment at the document level.
1629      * @param description the file description
1630      * @param fileStore an array with the file. If it's <CODE>null</CODE>
1631      * the file will be read from the disk
1632      * @param file the path to the file. It will only be used if
1633      * <CODE>fileStore</CODE> is not <CODE>null</CODE>
1634      * @param fileDisplay the actual file name stored in the pdf
1635      * @throws IOException on error
1636      */
1637     public void addFileAttachment(final String description, final byte fileStore[], final String file, final String fileDisplay) throws IOException {
1638         addFileAttachment(description, PdfFileSpecification.fileEmbedded(this, file, fileDisplay, fileStore));
1639     }
1640
1641     /**
1642      * Use this method to add a file attachment at the document level.
1643      * @param description the file description
1644      * @param fs the file specification
1645     * @throws IOException if the file attachment could not be added to the document
1646      */
1647     public void addFileAttachment(final String description, final PdfFileSpecification fs) throws IOException {
1648         pdf.addFileAttachment(description, fs);
1649     }
1650
1651     /**
1652      * Use this method to add a file attachment at the document level.
1653      * @param fs the file specification
1654     * @throws IOException if the file attachment could not be added to the document
1655      */
1656     public void addFileAttachment(final PdfFileSpecification fs) throws IOException {
1657         addFileAttachment(null, fs);
1658     }
1659
1660// [C6] Actions (open and additional)
1661
1662     /** action value */
1663     public static final PdfName DOCUMENT_CLOSE = PdfName.WC;
1664     /** action value */
1665     public static final PdfName WILL_SAVE = PdfName.WS;
1666     /** action value */
1667     public static final PdfName DID_SAVE = PdfName.DS;
1668     /** action value */
1669     public static final PdfName WILL_PRINT = PdfName.WP;
1670     /** action value */
1671     public static final PdfName DID_PRINT = PdfName.DP;
1672
1673    /** @see com.itextpdf.text.pdf.interfaces.PdfDocumentActions#setOpenAction(java.lang.String) */
1674    public void setOpenAction(final String name) {
1675         pdf.setOpenAction(name);
1676     }
1677
1678    /** @see com.itextpdf.text.pdf.interfaces.PdfDocumentActions#setOpenAction(com.itextpdf.text.pdf.PdfAction) */
1679    public void setOpenAction(final PdfAction action) {
1680         pdf.setOpenAction(action);
1681     }
1682
1683    /** @see com.itextpdf.text.pdf.interfaces.PdfDocumentActions#setAdditionalAction(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfAction) */
1684    public void setAdditionalAction(final PdfName actionType, final PdfAction action) throws DocumentException {
1685         if (!(actionType.equals(DOCUMENT_CLOSE) ||
1686         actionType.equals(WILL_SAVE) ||
1687         actionType.equals(DID_SAVE) ||
1688         actionType.equals(WILL_PRINT) ||
1689         actionType.equals(DID_PRINT))) {
1690             throw new DocumentException(MessageLocalization.getComposedMessage("invalid.additional.action.type.1", actionType.toString()));
1691         }
1692         pdf.addAdditionalAction(actionType, action);
1693     }
1694
1695//  [C7] portable collections
1696
1697    /**
1698     * Use this method to add the Collection dictionary.
1699     * @param collection a dictionary of type PdfCollection
1700     */
1701    public void setCollection(final PdfCollection collection) {
1702        setAtLeastPdfVersion(VERSION_1_7);
1703        pdf.setCollection(collection);
1704    }
1705
1706//  [C8] AcroForm
1707
1708    /** signature value */
1709    public static final int SIGNATURE_EXISTS = 1;
1710    /** signature value */
1711    public static final int SIGNATURE_APPEND_ONLY = 2;
1712
1713    /** @see com.itextpdf.text.pdf.interfaces.PdfAnnotations#getAcroForm() */
1714    public PdfAcroForm getAcroForm() {
1715        return pdf.getAcroForm();
1716    }
1717
1718    /** @see com.itextpdf.text.pdf.interfaces.PdfAnnotations#addAnnotation(com.itextpdf.text.pdf.PdfAnnotation) */
1719    public void addAnnotation(final PdfAnnotation annot) {
1720        pdf.addAnnotation(annot);
1721    }
1722
1723    void addAnnotation(final PdfAnnotation annot, final int page) {
1724        addAnnotation(annot);
1725    }
1726
1727    /** @see com.itextpdf.text.pdf.interfaces.PdfAnnotations#addCalculationOrder(com.itextpdf.text.pdf.PdfFormField) */
1728    public void addCalculationOrder(final PdfFormField annot) {
1729        pdf.addCalculationOrder(annot);
1730    }
1731
1732    /** @see com.itextpdf.text.pdf.interfaces.PdfAnnotations#setSigFlags(int) */
1733    public void setSigFlags(final int f) {
1734        pdf.setSigFlags(f);
1735    }
1736
1737//  [C9] Metadata
1738
1739    /** XMP Metadata for the document. */
1740    protected byte[] xmpMetadata = null;
1741
1742    /**
1743     * Use this method to set the XMP Metadata.
1744     * @param xmpMetadata The xmpMetadata to set.
1745     */
1746    public void setXmpMetadata(final byte[] xmpMetadata) {
1747        this.xmpMetadata = xmpMetadata;
1748    }
1749
1750    /**
1751     * Use this method to set the XMP Metadata for each page.
1752     * @param xmpMetadata The xmpMetadata to set.
1753     * @throws IOException
1754     */
1755    public void setPageXmpMetadata(final byte[] xmpMetadata) throws IOException {
1756        pdf.setXmpMetadata(xmpMetadata);
1757    }
1758
1759    /**
1760     * Use this method to creates XMP Metadata based
1761     * on the metadata in the PdfDocument.
1762     */
1763    public void createXmpMetadata() {
1764        setXmpMetadata(createXmpMetadataBytes());
1765    }
1766
1767    /**
1768     * @return an XmpMetadata byte array
1769     */
1770    private byte[] createXmpMetadataBytes() {
1771        ByteArrayOutputStream baos = new ByteArrayOutputStream();
1772        try {
1773            XmpWriter xmp = new XmpWriter(baos, pdf.getInfo(), pdfxConformance.getPDFXConformance());
1774            xmp.close();
1775        }
1776        catch (IOException ioe) {
1777            ioe.printStackTrace();
1778        }
1779        return baos.toByteArray();
1780    }
1781
1782//  [C10] PDFX Conformance
1783    /** A PDF/X level. */
1784    public static final int PDFXNONE = 0;
1785    /** A PDF/X level. */
1786    public static final int PDFX1A2001 = 1;
1787    /** A PDF/X level. */
1788    public static final int PDFX32002 = 2;
1789    /** PDFA-1A level. */
1790    public static final int PDFA1A = 3;
1791    /** PDFA-1B level. */
1792    public static final int PDFA1B = 4;
1793
1794    /** Stores the PDF/X level. */
1795    private final PdfXConformanceImp pdfxConformance = new PdfXConformanceImp();
1796
1797    /** @see com.itextpdf.text.pdf.interfaces.PdfXConformance#setPDFXConformance(int) */
1798    public void setPDFXConformance(final int pdfx) {
1799        if (pdfxConformance.getPDFXConformance() == pdfx)
1800            return;
1801        if (pdf.isOpen())
1802            throw new PdfXConformanceException(MessageLocalization.getComposedMessage("pdfx.conformance.can.only.be.set.before.opening.the.document"));
1803        if (crypto != null)
1804            throw new PdfXConformanceException(MessageLocalization.getComposedMessage("a.pdfx.conforming.document.cannot.be.encrypted"));
1805        if (pdfx == PDFA1A || pdfx == PDFA1B)
1806            setPdfVersion(VERSION_1_4);
1807        else if (pdfx != PDFXNONE)
1808            setPdfVersion(VERSION_1_3);
1809        pdfxConformance.setPDFXConformance(pdfx);
1810    }
1811
1812    /** @see com.itextpdf.text.pdf.interfaces.PdfXConformance#getPDFXConformance() */
1813    public int getPDFXConformance() {
1814        return pdfxConformance.getPDFXConformance();
1815    }
1816
1817    /** @see com.itextpdf.text.pdf.interfaces.PdfXConformance#isPdfX() */
1818    public boolean isPdfX() {
1819        return pdfxConformance.isPdfX();
1820    }
1821
1822//  [C11] Output intents
1823    /**
1824     * Sets the values of the output intent dictionary. Null values are allowed to
1825     * suppress any key.
1826     *
1827     * @param outputConditionIdentifier a value
1828     * @param outputCondition           a value, "PDFA/A" to force GTS_PDFA1, otherwise cued by pdfxConformance.
1829     * @param registryName              a value
1830     * @param info                      a value
1831     * @param colorProfile              a value
1832     * @since 2.1.5
1833     * @throws IOException on error
1834     */
1835    public void setOutputIntents(final String outputConditionIdentifier, final String outputCondition, final String registryName, final String info, final ICC_Profile colorProfile) throws IOException {
1836        getExtraCatalog();
1837        PdfDictionary out = new PdfDictionary(PdfName.OUTPUTINTENT);
1838        if (outputCondition != null)
1839            out.put(PdfName.OUTPUTCONDITION, new PdfString(outputCondition, PdfObject.TEXT_UNICODE));
1840        if (outputConditionIdentifier != null)
1841            out.put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString(outputConditionIdentifier, PdfObject.TEXT_UNICODE));
1842        if (registryName != null)
1843            out.put(PdfName.REGISTRYNAME, new PdfString(registryName, PdfObject.TEXT_UNICODE));
1844        if (info != null)
1845            out.put(PdfName.INFO, new PdfString(info, PdfObject.TEXT_UNICODE));
1846        if (colorProfile != null) {
1847            PdfStream stream = new PdfICCBased(colorProfile, compressionLevel);
1848            out.put(PdfName.DESTOUTPUTPROFILE, addToBody(stream).getIndirectReference());
1849        }
1850
1851        PdfName intentSubtype;
1852        if (pdfxConformance.isPdfA1() || "PDFA/1".equals(outputCondition)) {
1853            intentSubtype = PdfName.GTS_PDFA1;
1854        }
1855        else {
1856            intentSubtype = PdfName.GTS_PDFX;
1857        }
1858
1859        out.put(PdfName.S, intentSubtype);
1860
1861        extraCatalog.put(PdfName.OUTPUTINTENTS, new PdfArray(out));
1862    }
1863
1864   /**
1865     * Sets the values of the output intent dictionary. Null values are allowed to
1866     * suppress any key.
1867     *
1868     * Prefer the <CODE>ICC_Profile</CODE>-based version of this method.
1869     * @param outputConditionIdentifier a value
1870     * @param outputCondition           a value, "PDFA/A" to force GTS_PDFA1, otherwise cued by pdfxConformance.
1871     * @param registryName              a value
1872     * @param info                      a value
1873     * @param destOutputProfile         a value
1874     * @since 1.x
1875     *
1876     * @throws IOException
1877     */
1878    public void setOutputIntents(final String outputConditionIdentifier, final String outputCondition, final String registryName, final String info, final byte destOutputProfile[]) throws IOException {
1879        ICC_Profile colorProfile = destOutputProfile == null ? null : ICC_Profile.getInstance(destOutputProfile);
1880        setOutputIntents(outputConditionIdentifier, outputCondition, registryName, info, colorProfile);
1881    }
1882
1883
1884    /**
1885     * Use this method to copy the output intent dictionary
1886     * from another document to this one.
1887     * @param reader the other document
1888     * @param checkExistence <CODE>true</CODE> to just check for the existence of a valid output intent
1889     * dictionary, <CODE>false</CODE> to insert the dictionary if it exists
1890     * @throws IOException on error
1891     * @return <CODE>true</CODE> if the output intent dictionary exists, <CODE>false</CODE>
1892     * otherwise
1893     */
1894    public boolean setOutputIntents(final PdfReader reader, final boolean checkExistence) throws IOException {
1895        PdfDictionary catalog = reader.getCatalog();
1896        PdfArray outs = catalog.getAsArray(PdfName.OUTPUTINTENTS);
1897        if (outs == null)
1898            return false;
1899        if (outs.isEmpty())
1900            return false;
1901        PdfDictionary out = outs.getAsDict(0);
1902        PdfObject obj = PdfReader.getPdfObject(out.get(PdfName.S));
1903        if (obj == null || !PdfName.GTS_PDFX.equals(obj))
1904            return false;
1905        if (checkExistence)
1906            return true;
1907        PRStream stream = (PRStream)PdfReader.getPdfObject(out.get(PdfName.DESTOUTPUTPROFILE));
1908        byte destProfile[] = null;
1909        if (stream != null) {
1910            destProfile = PdfReader.getStreamBytes(stream);
1911        }
1912        setOutputIntents(getNameString(out, PdfName.OUTPUTCONDITIONIDENTIFIER), getNameString(out, PdfName.OUTPUTCONDITION),
1913            getNameString(out, PdfName.REGISTRYNAME), getNameString(out, PdfName.INFO), destProfile);
1914        return true;
1915    }
1916
1917    private static String getNameString(final PdfDictionary dic, final PdfName key) {
1918        PdfObject obj = PdfReader.getPdfObject(dic.get(key));
1919        if (obj == null || !obj.isString())
1920            return null;
1921        return ((PdfString)obj).toUnicodeString();
1922    }
1923
1924// PDF Objects that have an impact on the PDF body
1925
1926//  [F1] PdfEncryptionSettings interface
1927
1928    // types of encryption
1929
1930    /** Type of encryption */
1931    public static final int STANDARD_ENCRYPTION_40 = 0;
1932    /** Type of encryption */
1933    public static final int STANDARD_ENCRYPTION_128 = 1;
1934    /** Type of encryption */
1935    public static final int ENCRYPTION_AES_128 = 2;
1936    /** Type of encryption */
1937    public static final int ENCRYPTION_AES_256 = 3;
1938    /** Mask to separate the encryption type from the encryption mode. */
1939    static final int ENCRYPTION_MASK = 7;
1940    /** Add this to the mode to keep the metadata in clear text */
1941    public static final int DO_NOT_ENCRYPT_METADATA = 8;
1942    /**
1943     * Add this to the mode to keep encrypt only the embedded files.
1944     * @since 2.1.3
1945     */
1946    public static final int EMBEDDED_FILES_ONLY = 24;
1947
1948    // permissions
1949
1950    /** The operation permitted when the document is opened with the user password
1951     *
1952     * @since 2.0.7
1953     */
1954    public static final int ALLOW_PRINTING = 4 + 2048;
1955
1956    /** The operation permitted when the document is opened with the user password
1957     *
1958     * @since 2.0.7
1959     */
1960    public static final int ALLOW_MODIFY_CONTENTS = 8;
1961
1962    /** The operation permitted when the document is opened with the user password
1963     *
1964     * @since 2.0.7
1965     */
1966    public static final int ALLOW_COPY = 16;
1967
1968    /** The operation permitted when the document is opened with the user password
1969     *
1970     * @since 2.0.7
1971     */
1972    public static final int ALLOW_MODIFY_ANNOTATIONS = 32;
1973
1974    /** The operation permitted when the document is opened with the user password
1975     *
1976     * @since 2.0.7
1977     */
1978    public static final int ALLOW_FILL_IN = 256;
1979
1980    /** The operation permitted when the document is opened with the user password
1981     *
1982     * @since 2.0.7
1983     */
1984    public static final int ALLOW_SCREENREADERS = 512;
1985
1986    /** The operation permitted when the document is opened with the user password
1987     *
1988     * @since 2.0.7
1989     */
1990    public static final int ALLOW_ASSEMBLY = 1024;
1991
1992    /** The operation permitted when the document is opened with the user password
1993     *
1994     * @since 2.0.7
1995     */
1996    public static final int ALLOW_DEGRADED_PRINTING = 4;
1997
1998    /** @deprecated As of iText 2.0.7, use {@link #ALLOW_PRINTING} instead. Scheduled for removal at or after 2.2.0 */
1999    @Deprecated
2000    public static final int AllowPrinting = ALLOW_PRINTING;
2001    /** @deprecated As of iText 2.0.7, use {@link #ALLOW_MODIFY_CONTENTS} instead. Scheduled for removal at or after 2.2.0 */
2002    @Deprecated
2003    public static final int AllowModifyContents = ALLOW_MODIFY_CONTENTS;
2004    /** @deprecated As of iText 2.0.7, use {@link #ALLOW_COPY} instead. Scheduled for removal at or after 2.2.0 */
2005    @Deprecated
2006    public static final int AllowCopy = ALLOW_COPY;
2007    /** @deprecated As of iText 2.0.7, use {@link #ALLOW_MODIFY_ANNOTATIONS} instead. Scheduled for removal at or after 2.2.0 */
2008    @Deprecated
2009    public static final int AllowModifyAnnotations = ALLOW_MODIFY_ANNOTATIONS;
2010    /** @deprecated As of iText 2.0.7, use {@link #ALLOW_FILL_IN} instead. Scheduled for removal at or after 2.2.0 */
2011    @Deprecated
2012    public static final int AllowFillIn = ALLOW_FILL_IN;
2013    /** @deprecated As of iText 2.0.7, use {@link #ALLOW_SCREENREADERS} instead. Scheduled for removal at or after 2.2.0 */
2014    @Deprecated
2015    public static final int AllowScreenReaders = ALLOW_SCREENREADERS;
2016    /** @deprecated As of iText 2.0.7, use {@link #ALLOW_ASSEMBLY} instead. Scheduled for removal at or after 2.2.0 */
2017    @Deprecated
2018    public static final int AllowAssembly = ALLOW_ASSEMBLY;
2019    /** @deprecated As of iText 2.0.7, use {@link #ALLOW_DEGRADED_PRINTING} instead. Scheduled for removal at or after 2.2.0 */
2020    @Deprecated
2021    public static final int AllowDegradedPrinting = ALLOW_DEGRADED_PRINTING;
2022
2023    // Strength of the encryption (kept for historical reasons)
2024    /** @deprecated As of iText 2.0.7, use {@link #STANDARD_ENCRYPTION_40} instead. Scheduled for removal at or after 2.2.0 */
2025    @Deprecated
2026    public static final boolean STRENGTH40BITS = false;
2027    /** @deprecated As of iText 2.0.7, use {@link #STANDARD_ENCRYPTION_128} instead. Scheduled for removal at or after 2.2.0 */
2028    @Deprecated
2029    public static final boolean STRENGTH128BITS = true;
2030
2031    /** Contains the business logic for cryptography. */
2032    protected PdfEncryption crypto;
2033    PdfEncryption getEncryption() {
2034        return crypto;
2035    }
2036
2037    /** @see com.itextpdf.text.pdf.interfaces.PdfEncryptionSettings#setEncryption(byte[], byte[], int, int) */
2038    public void setEncryption(final byte userPassword[], final byte ownerPassword[], final int permissions, final int encryptionType) throws DocumentException {
2039        if (pdf.isOpen())
2040            throw new DocumentException(MessageLocalization.getComposedMessage("encryption.can.only.be.added.before.opening.the.document"));
2041        crypto = new PdfEncryption();
2042        crypto.setCryptoMode(encryptionType, 0);
2043        crypto.setupAllKeys(userPassword, ownerPassword, permissions);
2044    }
2045
2046    /** @see com.itextpdf.text.pdf.interfaces.PdfEncryptionSettings#setEncryption(java.security.cert.Certificate[], int[], int) */
2047    public void setEncryption(final Certificate[] certs, final int[] permissions, final int encryptionType) throws DocumentException {
2048        if (pdf.isOpen())
2049            throw new DocumentException(MessageLocalization.getComposedMessage("encryption.can.only.be.added.before.opening.the.document"));
2050        crypto = new PdfEncryption();
2051        if (certs != null) {
2052            for (int i=0; i < certs.length; i++) {
2053                crypto.addRecipient(certs[i], permissions[i]);
2054            }
2055        }
2056        crypto.setCryptoMode(encryptionType, 0);
2057        crypto.getEncryptionDictionary();
2058    }
2059
2060    /**
2061     * Sets the encryption options for this document. The userPassword and the
2062     *  ownerPassword can be null or have zero length. In this case the ownerPassword
2063     *  is replaced by a random string. The open permissions for the document can be
2064     *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
2065     *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
2066     *  The permissions can be combined by ORing them.
2067     * @param userPassword the user password. Can be null or empty
2068     * @param ownerPassword the owner password. Can be null or empty
2069     * @param permissions the user permissions
2070     * @param strength128Bits <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length
2071     * @throws DocumentException if the document is already open
2072     * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], int, int)}. Scheduled for removal at or after 2.2.0
2073     */
2074    @Deprecated
2075    public void setEncryption(final byte userPassword[], final byte ownerPassword[], final int permissions, final boolean strength128Bits) throws DocumentException {
2076        setEncryption(userPassword, ownerPassword, permissions, strength128Bits ? STANDARD_ENCRYPTION_128 : STANDARD_ENCRYPTION_40);
2077    }
2078
2079    /**
2080     * Sets the encryption options for this document. The userPassword and the
2081     *  ownerPassword can be null or have zero length. In this case the ownerPassword
2082     *  is replaced by a random string. The open permissions for the document can be
2083     *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
2084     *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
2085     *  The permissions can be combined by ORing them.
2086     * @param strength <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length
2087     * @param userPassword the user password. Can be null or empty
2088     * @param ownerPassword the owner password. Can be null or empty
2089     * @param permissions the user permissions
2090     * @throws DocumentException if the document is already open
2091     * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], int, int)}. Scheduled for removal at or after 2.2.0
2092     */
2093    @Deprecated
2094    public void setEncryption(final boolean strength, final String userPassword, final String ownerPassword, final int permissions) throws DocumentException {
2095        setEncryption(getISOBytes(userPassword), getISOBytes(ownerPassword), permissions, strength ? STANDARD_ENCRYPTION_128 : STANDARD_ENCRYPTION_40);
2096    }
2097
2098    /**
2099     * Sets the encryption options for this document. The userPassword and the
2100     *  ownerPassword can be null or have zero length. In this case the ownerPassword
2101     *  is replaced by a random string. The open permissions for the document can be
2102     *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
2103     *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
2104     *  The permissions can be combined by ORing them.
2105     * @param encryptionType the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128 or ENCRYPTION_AES128.
2106     * Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext
2107     * @param userPassword the user password. Can be null or empty
2108     * @param ownerPassword the owner password. Can be null or empty
2109     * @param permissions the user permissions
2110     * @throws DocumentException if the document is already open
2111     * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], int, int)}. Scheduled for removal at or after 2.2.0
2112     */
2113    @Deprecated
2114    public void setEncryption(final int encryptionType, final String userPassword, final String ownerPassword, final int permissions) throws DocumentException {
2115        setEncryption(getISOBytes(userPassword), getISOBytes(ownerPassword), permissions, encryptionType);
2116    }
2117
2118//  [F2] compression
2119
2120    /** Holds value of property fullCompression. */
2121    protected boolean fullCompression = false;
2122
2123    /**
2124     * Use this method to find out if 1.5 compression is on.
2125     * @return the 1.5 compression status
2126     */
2127    public boolean isFullCompression() {
2128        return this.fullCompression;
2129    }
2130
2131    /**
2132     * Use this method to set the document's compression to the
2133     * PDF 1.5 mode with object streams and xref streams.
2134     * It can be set at any time but once set it can't be unset.
2135     * <p>
2136     * If set before opening the document it will also set the pdf version to 1.5.
2137     */
2138    public void setFullCompression() {
2139        this.fullCompression = true;
2140        setAtLeastPdfVersion(VERSION_1_5);
2141    }
2142
2143    /**
2144     * The compression level of the content streams.
2145     * @since 2.1.3
2146     */
2147    protected int compressionLevel = PdfStream.DEFAULT_COMPRESSION;
2148
2149    /**
2150     * Returns the compression level used for streams written by this writer.
2151     * @return the compression level (0 = best speed, 9 = best compression, -1 is default)
2152     * @since 2.1.3
2153     */
2154    public int getCompressionLevel() {
2155        return compressionLevel;
2156    }
2157
2158    /**
2159     * Sets the compression level to be used for streams written by this writer.
2160     * @param compressionLevel a value between 0 (best speed) and 9 (best compression)
2161     * @since 2.1.3
2162     */
2163    public void setCompressionLevel(final int compressionLevel) {
2164        if (compressionLevel < PdfStream.NO_COMPRESSION || compressionLevel > PdfStream.BEST_COMPRESSION)
2165            this.compressionLevel = PdfStream.DEFAULT_COMPRESSION;
2166        else
2167            this.compressionLevel = compressionLevel;
2168    }
2169
2170//  [F3] adding fonts
2171
2172    /** The fonts of this document */
2173    protected LinkedHashMap<BaseFont, FontDetails> documentFonts = new LinkedHashMap<BaseFont, FontDetails>();
2174
2175    /** The font number counter for the fonts in the document. */
2176    protected int fontNumber = 1;
2177
2178    /**
2179     * Adds a <CODE>BaseFont</CODE> to the document but not to the page resources.
2180     * It is used for templates.
2181     * @param bf the <CODE>BaseFont</CODE> to add
2182     * @return an <CODE>Object[]</CODE> where position 0 is a <CODE>PdfName</CODE>
2183     * and position 1 is an <CODE>PdfIndirectReference</CODE>
2184     */
2185
2186    FontDetails addSimple(final BaseFont bf) {
2187        if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) {
2188            return new FontDetails(new PdfName("F" + fontNumber++), ((DocumentFont)bf).getIndirectReference(), bf);
2189        }
2190        FontDetails ret = documentFonts.get(bf);
2191        if (ret == null) {
2192            PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_FONT, bf);
2193            ret = new FontDetails(new PdfName("F" + fontNumber++), body.getPdfIndirectReference(), bf);
2194            documentFonts.put(bf, ret);
2195        }
2196        return ret;
2197    }
2198
2199    void eliminateFontSubset(final PdfDictionary fonts) {
2200        for (Object element : documentFonts.values()) {
2201            FontDetails ft = (FontDetails)element;
2202            if (fonts.get(ft.getFontName()) != null)
2203                ft.setSubset(false);
2204        }
2205    }
2206
2207//  [F4] adding (and releasing) form XObjects
2208
2209    /** The form XObjects in this document. The key is the xref and the value
2210        is Object[]{PdfName, template}.*/
2211    protected HashMap<PdfIndirectReference, Object[]> formXObjects = new HashMap<PdfIndirectReference, Object[]>();
2212
2213    /** The name counter for the form XObjects name. */
2214    protected int formXObjectsCounter = 1;
2215
2216    /**
2217     * Adds a template to the document but not to the page resources.
2218     * @param template the template to add
2219     * @param forcedName the template name, rather than a generated one. Can be null
2220     * @return the <CODE>PdfName</CODE> for this template
2221     */
2222
2223    PdfName addDirectTemplateSimple(PdfTemplate template, final PdfName forcedName) {
2224        PdfIndirectReference ref = template.getIndirectReference();
2225        Object obj[] = formXObjects.get(ref);
2226        PdfName name = null;
2227        try {
2228            if (obj == null) {
2229                if (forcedName == null) {
2230                    name = new PdfName("Xf" + formXObjectsCounter);
2231                    ++formXObjectsCounter;
2232                }
2233                else
2234                    name = forcedName;
2235                if (template.getType() == PdfTemplate.TYPE_IMPORTED) {
2236                    // If we got here from PdfCopy we'll have to fill importedPages
2237                    PdfImportedPage ip = (PdfImportedPage)template;
2238                    PdfReader r = ip.getPdfReaderInstance().getReader();
2239                    if (!readerInstances.containsKey(r)) {
2240                        readerInstances.put(r, ip.getPdfReaderInstance());
2241                    }
2242                    template = null;
2243                }
2244                formXObjects.put(ref, new Object[]{name, template});
2245            }
2246            else
2247                name = (PdfName)obj[0];
2248        }
2249        catch (Exception e) {
2250            throw new ExceptionConverter(e);
2251        }
2252        return name;
2253    }
2254
2255    /**
2256     * Use this method to releases the memory used by a template.
2257     * This method writes the template to the output.
2258     * The template can still be added to any content
2259     * but changes to the template itself won't have any effect.
2260     * @param tp the template to release
2261     * @throws IOException on error
2262     */
2263    public void releaseTemplate(final PdfTemplate tp) throws IOException {
2264        PdfIndirectReference ref = tp.getIndirectReference();
2265        Object[] objs = formXObjects.get(ref);
2266        if (objs == null || objs[1] == null)
2267            return;
2268        PdfTemplate template = (PdfTemplate)objs[1];
2269        if (template.getIndirectReference() instanceof PRIndirectReference)
2270            return;
2271        if (template.getType() == PdfTemplate.TYPE_TEMPLATE) {
2272            addToBody(template.getFormXObject(compressionLevel), template.getIndirectReference());
2273            objs[1] = null;
2274        }
2275    }
2276
2277//  [F5] adding pages imported form other PDF documents
2278
2279    /**
2280     * Instances of PdfReader/PdfReaderInstance that are used to import pages.
2281     * @since 5.0.3
2282     */
2283    protected HashMap<PdfReader, PdfReaderInstance> readerInstances = new HashMap<PdfReader, PdfReaderInstance>();
2284
2285    /**
2286     * Use this method to get a page from other PDF document.
2287     * The page can be used as any other PdfTemplate.
2288     * Note that calling this method more than once with the same parameters
2289     * will retrieve the same object.
2290     * @param reader the PDF document where the page is
2291     * @param pageNumber the page number. The first page is 1
2292     * @return the template representing the imported page
2293     */
2294    public PdfImportedPage getImportedPage(final PdfReader reader, final int pageNumber) {
2295        return getPdfReaderInstance(reader).getImportedPage(pageNumber);
2296    }
2297
2298    /**
2299     * Returns the PdfReaderInstance associated with the specified reader.
2300     * Multiple calls with the same reader object will return the same
2301     * PdfReaderInstance.
2302     * @param reader the PDF reader that you want an instance for
2303     * @return the instance for the provided reader
2304     * @since 5.0.3
2305     */
2306    protected PdfReaderInstance getPdfReaderInstance(final PdfReader reader){
2307        PdfReaderInstance inst = readerInstances.get(reader);
2308        if (inst == null) {
2309            inst = reader.getPdfReaderInstance(this);
2310            readerInstances.put(reader, inst);
2311        }
2312        return inst;
2313    }
2314
2315    /**
2316     * Use this method to writes the reader to the document
2317     * and free the memory used by it.
2318     * The main use is when concatenating multiple documents
2319     * to keep the memory usage restricted to the current
2320     * appending document.
2321     * @param reader the <CODE>PdfReader</CODE> to free
2322     * @throws IOException on error
2323     */
2324    public void freeReader(final PdfReader reader) throws IOException {
2325        currentPdfReaderInstance = readerInstances.get(reader);
2326        if (currentPdfReaderInstance == null)
2327            return;
2328        currentPdfReaderInstance.writeAllPages();
2329        currentPdfReaderInstance = null;
2330        readerInstances.remove(reader);
2331    }
2332
2333    /**
2334     * Use this method to gets the current document size.
2335     * This size only includes the data already written
2336     * to the output stream, it does not include templates or fonts.
2337     * It is useful if used with <CODE>freeReader()</CODE>
2338     * when concatenating many documents and an idea of
2339     * the current size is needed.
2340     * @return the approximate size without fonts or templates
2341     */
2342    public int getCurrentDocumentSize() {
2343        return body.offset() + body.size() * 20 + 0x48;
2344    }
2345
2346    protected PdfReaderInstance currentPdfReaderInstance;
2347
2348    protected int getNewObjectNumber(final PdfReader reader, final int number, final int generation) {
2349        if (currentPdfReaderInstance == null) {
2350                currentPdfReaderInstance = getPdfReaderInstance(reader);
2351        }
2352        return currentPdfReaderInstance.getNewObjectNumber(number, generation);
2353    }
2354
2355    RandomAccessFileOrArray getReaderFile(final PdfReader reader) {
2356        return currentPdfReaderInstance.getReaderFile();
2357    }
2358
2359//  [F6] spot colors
2360
2361    /** The colors of this document */
2362    protected HashMap<PdfSpotColor, ColorDetails> documentColors = new HashMap<PdfSpotColor, ColorDetails>();
2363
2364    /** The color number counter for the colors in the document. */
2365    protected int colorNumber = 1;
2366
2367    PdfName getColorspaceName() {
2368        return new PdfName("CS" + colorNumber++);
2369    }
2370
2371    /**
2372     * Adds a <CODE>SpotColor</CODE> to the document but not to the page resources.
2373     * @param spc the <CODE>SpotColor</CODE> to add
2374     * @return an <CODE>Object[]</CODE> where position 0 is a <CODE>PdfName</CODE>
2375     * and position 1 is an <CODE>PdfIndirectReference</CODE>
2376     */
2377    ColorDetails addSimple(final PdfSpotColor spc) {
2378        ColorDetails ret = documentColors.get(spc);
2379        if (ret == null) {
2380            ret = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), spc);
2381            documentColors.put(spc, ret);
2382        }
2383        return ret;
2384    }
2385
2386//  [F7] document patterns
2387
2388    /** The patterns of this document */
2389    protected HashMap<PdfPatternPainter, PdfName> documentPatterns = new HashMap<PdfPatternPainter, PdfName>();
2390
2391    /** The pattern number counter for the colors in the document. */
2392    protected int patternNumber = 1;
2393
2394    PdfName addSimplePattern(final PdfPatternPainter painter) {
2395        PdfName name = documentPatterns.get(painter);
2396        try {
2397            if ( name == null ) {
2398                name = new PdfName("P" + patternNumber);
2399                ++patternNumber;
2400                documentPatterns.put(painter, name);
2401            }
2402        } catch (Exception e) {
2403            throw new ExceptionConverter(e);
2404        }
2405        return name;
2406    }
2407
2408//  [F8] shading patterns
2409
2410    protected HashSet<PdfShadingPattern> documentShadingPatterns = new HashSet<PdfShadingPattern>();
2411
2412    void addSimpleShadingPattern(final PdfShadingPattern shading) {
2413        if (!documentShadingPatterns.contains(shading)) {
2414            shading.setName(patternNumber);
2415            ++patternNumber;
2416            documentShadingPatterns.add(shading);
2417            addSimpleShading(shading.getShading());
2418        }
2419    }
2420
2421//  [F9] document shadings
2422
2423    protected HashSet<PdfShading> documentShadings = new HashSet<PdfShading>();
2424
2425    void addSimpleShading(final PdfShading shading) {
2426        if (!documentShadings.contains(shading)) {
2427            documentShadings.add(shading);
2428            shading.setName(documentShadings.size());
2429        }
2430    }
2431
2432// [F10] extended graphics state (for instance for transparency)
2433
2434    protected HashMap<PdfDictionary, PdfObject[]> documentExtGState = new HashMap<PdfDictionary, PdfObject[]>();
2435
2436    PdfObject[] addSimpleExtGState(final PdfDictionary gstate) {
2437        if (!documentExtGState.containsKey(gstate)) {
2438            PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_GSTATE, gstate);
2439            documentExtGState.put(gstate, new PdfObject[]{new PdfName("GS" + (documentExtGState.size() + 1)), getPdfIndirectReference()});
2440        }
2441        return documentExtGState.get(gstate);
2442    }
2443
2444//  [F11] adding properties (OCG, marked content)
2445
2446    protected HashMap<Object, PdfObject[]> documentProperties = new HashMap<Object, PdfObject[]>();
2447    PdfObject[] addSimpleProperty(final Object prop, final PdfIndirectReference refi) {
2448        if (!documentProperties.containsKey(prop)) {
2449            if (prop instanceof PdfOCG)
2450                PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_LAYER, null);
2451            documentProperties.put(prop, new PdfObject[]{new PdfName("Pr" + (documentProperties.size() + 1)), refi});
2452        }
2453        return documentProperties.get(prop);
2454    }
2455
2456    boolean propertyExists(final Object prop) {
2457        return documentProperties.containsKey(prop);
2458    }
2459
2460//  [F12] tagged PDF
2461
2462    protected boolean tagged = false;
2463    protected PdfStructureTreeRoot structureTreeRoot;
2464
2465    /**
2466     * Mark this document for tagging. It must be called before open.
2467     */
2468    public void setTagged() {
2469        if (open)
2470            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("tagging.must.be.set.before.opening.the.document"));
2471        tagged = true;
2472    }
2473
2474    /**
2475     * Check if the document is marked for tagging.
2476     * @return <CODE>true</CODE> if the document is marked for tagging
2477     */
2478    public boolean isTagged() {
2479        return tagged;
2480    }
2481
2482    /**
2483     * Gets the structure tree root. If the document is not marked for tagging it will return <CODE>null</CODE>.
2484     * @return the structure tree root
2485     */
2486    public PdfStructureTreeRoot getStructureTreeRoot() {
2487        if (tagged && structureTreeRoot == null)
2488            structureTreeRoot = new PdfStructureTreeRoot(this);
2489        return structureTreeRoot;
2490    }
2491
2492//  [F13] Optional Content Groups
2493    /** A hashSet containing all the PdfLayer objects. */
2494    protected HashSet<PdfOCG> documentOCG = new HashSet<PdfOCG>();
2495    /** An array list used to define the order of an OCG tree. */
2496    protected ArrayList<PdfOCG> documentOCGorder = new ArrayList<PdfOCG>();
2497    /** The OCProperties in a catalog dictionary. */
2498    protected PdfOCProperties OCProperties;
2499    /** The RBGroups array in an OCG dictionary */
2500    protected PdfArray OCGRadioGroup = new PdfArray();
2501    /**
2502     * The locked array in an OCG dictionary
2503     * @since   2.1.2
2504     */
2505    protected PdfArray OCGLocked = new PdfArray();
2506
2507    /**
2508     * Use this method to get the <B>Optional Content Properties Dictionary</B>.
2509     * Each call fills the dictionary with the current layer state.
2510     * It's advisable to only call this method right before close
2511     * and do any modifications at that time.
2512     * @return the Optional Content Properties Dictionary
2513     */
2514    public PdfOCProperties getOCProperties() {
2515        fillOCProperties(true);
2516        return OCProperties;
2517    }
2518
2519    /**
2520     * Use this method to set a collection of optional content groups
2521     * whose states are intended to follow a "radio button" paradigm.
2522     * That is, the state of at most one optional content group
2523     * in the array should be ON at a time: if one group is turned
2524     * ON, all others must be turned OFF.
2525     * @param group the radio group
2526     */
2527    public void addOCGRadioGroup(final ArrayList<PdfLayer> group) {
2528        PdfArray ar = new PdfArray();
2529        for (int k = 0; k < group.size(); ++k) {
2530            PdfLayer layer = group.get(k);
2531            if (layer.getTitle() == null)
2532                ar.add(layer.getRef());
2533        }
2534        if (ar.size() == 0)
2535            return;
2536        OCGRadioGroup.add(ar);
2537    }
2538
2539    /**
2540     * Use this method to lock an optional content group.
2541     * The state of a locked group cannot be changed through the user interface
2542     * of a viewer application. Producers can use this entry to prevent the visibility
2543     * of content that depends on these groups from being changed by users.
2544     * @param layer     the layer that needs to be added to the array of locked OCGs
2545     * @since   2.1.2
2546     */
2547    public void lockLayer(final PdfLayer layer) {
2548        OCGLocked.add(layer.getRef());
2549    }
2550
2551    private static void getOCGOrder(final PdfArray order, final PdfLayer layer) {
2552        if (!layer.isOnPanel())
2553            return;
2554        if (layer.getTitle() == null)
2555            order.add(layer.getRef());
2556        ArrayList<PdfLayer> children = layer.getChildren();
2557        if (children == null)
2558            return;
2559        PdfArray kids = new PdfArray();
2560        if (layer.getTitle() != null)
2561            kids.add(new PdfString(layer.getTitle(), PdfObject.TEXT_UNICODE));
2562        for (int k = 0; k < children.size(); ++k) {
2563            getOCGOrder(kids, children.get(k));
2564        }
2565        if (kids.size() > 0)
2566            order.add(kids);
2567    }
2568
2569    private void addASEvent(final PdfName event, final PdfName category) {
2570        PdfArray arr = new PdfArray();
2571        for (Object element : documentOCG) {
2572            PdfLayer layer = (PdfLayer)element;
2573            PdfDictionary usage = layer.getAsDict(PdfName.USAGE);
2574            if (usage != null && usage.get(category) != null)
2575                arr.add(layer.getRef());
2576        }
2577        if (arr.size() == 0)
2578            return;
2579        PdfDictionary d = OCProperties.getAsDict(PdfName.D);
2580        PdfArray arras = d.getAsArray(PdfName.AS);
2581        if (arras == null) {
2582            arras = new PdfArray();
2583            d.put(PdfName.AS, arras);
2584        }
2585        PdfDictionary as = new PdfDictionary();
2586        as.put(PdfName.EVENT, event);
2587        as.put(PdfName.CATEGORY, new PdfArray(category));
2588        as.put(PdfName.OCGS, arr);
2589        arras.add(as);
2590    }
2591
2592    /**
2593     * @param erase true to erase the {@link PdfName#OCGS} and {@link PdfName#D} from the OCProperties first.
2594     * @since 2.1.2
2595     */
2596    protected void fillOCProperties(final boolean erase) {
2597        if (OCProperties == null)
2598            OCProperties = new PdfOCProperties();
2599        if (erase) {
2600            OCProperties.remove(PdfName.OCGS);
2601            OCProperties.remove(PdfName.D);
2602        }
2603        if (OCProperties.get(PdfName.OCGS) == null) {
2604            PdfArray gr = new PdfArray();
2605            for (Object element : documentOCG) {
2606                PdfLayer layer = (PdfLayer)element;
2607                gr.add(layer.getRef());
2608            }
2609            OCProperties.put(PdfName.OCGS, gr);
2610        }
2611        if (OCProperties.get(PdfName.D) != null)
2612            return;
2613        ArrayList<PdfOCG> docOrder = new ArrayList<PdfOCG>(documentOCGorder);
2614        for (Iterator<PdfOCG> it = docOrder.iterator(); it.hasNext();) {
2615            PdfLayer layer = (PdfLayer)it.next();
2616            if (layer.getParent() != null)
2617                it.remove();
2618        }
2619        PdfArray order = new PdfArray();
2620        for (Object element : docOrder) {
2621            PdfLayer layer = (PdfLayer)element;
2622            getOCGOrder(order, layer);
2623        }
2624        PdfDictionary d = new PdfDictionary();
2625        OCProperties.put(PdfName.D, d);
2626        d.put(PdfName.ORDER, order);
2627        PdfArray gr = new PdfArray();
2628        for (Object element : documentOCG) {
2629            PdfLayer layer = (PdfLayer)element;
2630            if (!layer.isOn())
2631                gr.add(layer.getRef());
2632        }
2633        if (gr.size() > 0)
2634            d.put(PdfName.OFF, gr);
2635        if (OCGRadioGroup.size() > 0)
2636            d.put(PdfName.RBGROUPS, OCGRadioGroup);
2637        if (OCGLocked.size() > 0)
2638            d.put(PdfName.LOCKED, OCGLocked);
2639        addASEvent(PdfName.VIEW, PdfName.ZOOM);
2640        addASEvent(PdfName.VIEW, PdfName.VIEW);
2641        addASEvent(PdfName.PRINT, PdfName.PRINT);
2642        addASEvent(PdfName.EXPORT, PdfName.EXPORT);
2643        d.put(PdfName.LISTMODE, PdfName.VISIBLEPAGES);
2644    }
2645
2646    void registerLayer(final PdfOCG layer) {
2647        PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_LAYER, null);
2648        if (layer instanceof PdfLayer) {
2649            PdfLayer la = (PdfLayer)layer;
2650            if (la.getTitle() == null) {
2651                if (!documentOCG.contains(layer)) {
2652                    documentOCG.add(layer);
2653                    documentOCGorder.add(layer);
2654                }
2655            }
2656            else {
2657                documentOCGorder.add(layer);
2658            }
2659        }
2660        else
2661            throw new IllegalArgumentException(MessageLocalization.getComposedMessage("only.pdflayer.is.accepted"));
2662    }
2663
2664//  User methods to change aspects of the page
2665
2666//  [U1] page size
2667
2668    /**
2669     * Use this method to get the size of the media box.
2670     * @return a Rectangle
2671     */
2672    public Rectangle getPageSize() {
2673        return pdf.getPageSize();
2674    }
2675
2676    /**
2677     * Use this method to set the crop box.
2678     * The crop box should not be rotated even if the page is rotated.
2679     * This change only takes effect in the next page.
2680     * @param crop the crop box
2681     */
2682    public void setCropBoxSize(final Rectangle crop) {
2683        pdf.setCropBoxSize(crop);
2684    }
2685
2686    /**
2687     * Use this method to set the page box sizes.
2688     * Allowed names are: "crop", "trim", "art" and "bleed".
2689     * @param boxName the box size
2690     * @param size the size
2691     */
2692    public void setBoxSize(final String boxName, final Rectangle size) {
2693        pdf.setBoxSize(boxName, size);
2694    }
2695
2696    /**
2697     * Use this method to get the size of a trim, art, crop or bleed box,
2698     * or null if not defined.
2699     * @param boxName crop, trim, art or bleed
2700     */
2701    public Rectangle getBoxSize(final String boxName) {
2702        return pdf.getBoxSize(boxName);
2703    }
2704
2705//  [U2] take care of empty pages
2706
2707    /**
2708     * Use this method to make sure a page is added,
2709     * even if it's empty. If you use setPageEmpty(false),
2710     * invoking newPage() after a blank page will add a newPage.
2711     * setPageEmpty(true) won't have any effect.
2712     * @param pageEmpty the state
2713     */
2714    public void setPageEmpty(final boolean pageEmpty) {
2715        if (pageEmpty)
2716            return;
2717        pdf.setPageEmpty(pageEmpty);
2718    }
2719
2720    /**
2721     * Checks if a newPage() will actually generate a new page.
2722     * @return true if a new page will be generated, false otherwise
2723     * @since 2.1.8
2724     */
2725    public boolean isPageEmpty() {
2726        return pdf.isPageEmpty();
2727    }
2728
2729//  [U3] page actions (open and close)
2730
2731    /** action value */
2732    public static final PdfName PAGE_OPEN = PdfName.O;
2733    /** action value */
2734    public static final PdfName PAGE_CLOSE = PdfName.C;
2735
2736    /** @see com.itextpdf.text.pdf.interfaces.PdfPageActions#setPageAction(com.itextpdf.text.pdf.PdfName, com.itextpdf.text.pdf.PdfAction) */
2737    public void setPageAction(final PdfName actionType, final PdfAction action) throws DocumentException {
2738          if (!actionType.equals(PAGE_OPEN) && !actionType.equals(PAGE_CLOSE))
2739              throw new DocumentException(MessageLocalization.getComposedMessage("invalid.page.additional.action.type.1", actionType.toString()));
2740          pdf.setPageAction(actionType, action);
2741      }
2742
2743    /** @see com.itextpdf.text.pdf.interfaces.PdfPageActions#setDuration(int) */
2744    public void setDuration(final int seconds) {
2745         pdf.setDuration(seconds);
2746     }
2747
2748    /** @see com.itextpdf.text.pdf.interfaces.PdfPageActions#setTransition(com.itextpdf.text.pdf.PdfTransition) */
2749    public void setTransition(final PdfTransition transition) {
2750         pdf.setTransition(transition);
2751     }
2752
2753//  [U4] Thumbnail image
2754
2755    /**
2756     * Use this method to set the thumbnail image for the current page.
2757     * @param image the image
2758     * @throws PdfException on error
2759     * @throws DocumentException or error
2760     */
2761    public void setThumbnail(final Image image) throws PdfException, DocumentException {
2762        pdf.setThumbnail(image);
2763    }
2764
2765//  [U5] Transparency groups
2766
2767    /**
2768     * A group attributes dictionary specifying the attributes
2769     * of the page's page group for use in the transparent
2770     * imaging model
2771     */
2772    protected PdfDictionary group;
2773
2774    /**
2775     * Use this method to get the group dictionary.
2776     * @return Value of property group.
2777     */
2778    public PdfDictionary getGroup() {
2779        return this.group;
2780    }
2781
2782    /**
2783     * Use this method to set the group dictionary.
2784     * @param group New value of property group.
2785     */
2786    public void setGroup(final PdfDictionary group) {
2787        this.group = group;
2788    }
2789
2790//  [U6] space char ratio
2791
2792    /** The default space-char ratio. */
2793    public static final float SPACE_CHAR_RATIO_DEFAULT = 2.5f;
2794    /** Disable the inter-character spacing. */
2795    public static final float NO_SPACE_CHAR_RATIO = 10000000f;
2796
2797    /**
2798     * The ratio between the extra word spacing and the extra character spacing.
2799     * Extra word spacing will grow <CODE>ratio</CODE> times more than extra character spacing.
2800     */
2801    private float spaceCharRatio = SPACE_CHAR_RATIO_DEFAULT;
2802
2803    /**
2804     * Use this method to gets the space/character extra spacing ratio
2805     * for fully justified text.
2806     * @return the space/character extra spacing ratio
2807     */
2808    public float getSpaceCharRatio() {
2809        return spaceCharRatio;
2810    }
2811
2812    /**
2813     * Use this method to set the ratio between the extra word spacing and
2814     * the extra character spacing when the text is fully justified.
2815     * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more
2816     * than extra character spacing. If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE>
2817     * then the extra character spacing will be zero.
2818     * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
2819     */
2820    public void setSpaceCharRatio(final float spaceCharRatio) {
2821        if (spaceCharRatio < 0.001f)
2822            this.spaceCharRatio = 0.001f;
2823        else
2824            this.spaceCharRatio = spaceCharRatio;
2825    }
2826
2827//  [U7] run direction (doesn't actually do anything)
2828
2829    /** Use the default run direction. */
2830    public static final int RUN_DIRECTION_DEFAULT = 0;
2831    /** Do not use bidirectional reordering. */
2832    public static final int RUN_DIRECTION_NO_BIDI = 1;
2833    /** Use bidirectional reordering with left-to-right
2834     * preferential run direction.
2835     */
2836    public static final int RUN_DIRECTION_LTR = 2;
2837    /** Use bidirectional reordering with right-to-left
2838     * preferential run direction.
2839     */
2840    public static final int RUN_DIRECTION_RTL = 3;
2841
2842    protected int runDirection = RUN_DIRECTION_NO_BIDI;
2843
2844    /**
2845     * Use this method to set the run direction.
2846     * This is only used as a placeholder as it does not affect anything.
2847     * @param runDirection the run direction
2848     */
2849    public void setRunDirection(final int runDirection) {
2850        if (runDirection < RUN_DIRECTION_NO_BIDI || runDirection > RUN_DIRECTION_RTL)
2851            throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.run.direction.1", runDirection));
2852        this.runDirection = runDirection;
2853    }
2854
2855    /**
2856     * Use this method to set the run direction.
2857     * @return the run direction
2858     */
2859    public int getRunDirection() {
2860        return runDirection;
2861    }
2862
2863//  [U8] user units
2864    /**
2865     * Use this method to set the user unit.
2866     * A UserUnit is a value that defines the default user space unit.
2867     * The minimum UserUnit is 1 (1 unit = 1/72 inch).
2868     * The maximum UserUnit is 75,000.
2869     * Note that this userunit only works starting with PDF1.6!
2870     * @param userunit The userunit to set.
2871     * @throws DocumentException on error
2872     */
2873     public void setUserunit(final float userunit) throws DocumentException {
2874                if (userunit < 1f || userunit > 75000f) throw new DocumentException(MessageLocalization.getComposedMessage("userunit.should.be.a.value.between.1.and.75000"));
2875         addPageDictEntry(PdfName.USERUNIT, new PdfNumber(userunit));
2876         setAtLeastPdfVersion(VERSION_1_6);
2877     }
2878
2879// Miscellaneous topics
2880
2881//  [M1] Color settings
2882
2883    protected PdfDictionary defaultColorspace = new PdfDictionary();
2884    /**
2885     * Use this method to get the default colorspaces.
2886     * @return the default colorspaces
2887     */
2888    public PdfDictionary getDefaultColorspace() {
2889        return defaultColorspace;
2890    }
2891
2892    /**
2893     * Use this method to sets the default colorspace that will be applied
2894     * to all the document. The colorspace is only applied if another colorspace
2895     * with the same name is not present in the content.
2896     * <p>
2897     * The colorspace is applied immediately when creating templates and
2898     * at the page end for the main document content.
2899     * @param key the name of the colorspace. It can be <CODE>PdfName.DEFAULTGRAY</CODE>, <CODE>PdfName.DEFAULTRGB</CODE>
2900     * or <CODE>PdfName.DEFAULTCMYK</CODE>
2901     * @param cs the colorspace. A <CODE>null</CODE> or <CODE>PdfNull</CODE> removes any colorspace with the same name
2902     */
2903    public void setDefaultColorspace(final PdfName key, final PdfObject cs) {
2904        if (cs == null || cs.isNull())
2905            defaultColorspace.remove(key);
2906        defaultColorspace.put(key, cs);
2907    }
2908
2909//  [M2] spot patterns
2910
2911    protected HashMap<ColorDetails, ColorDetails> documentSpotPatterns = new HashMap<ColorDetails, ColorDetails>();
2912    protected ColorDetails patternColorspaceRGB;
2913    protected ColorDetails patternColorspaceGRAY;
2914    protected ColorDetails patternColorspaceCMYK;
2915
2916    ColorDetails addSimplePatternColorspace(final BaseColor color) {
2917        int type = ExtendedColor.getType(color);
2918        if (type == ExtendedColor.TYPE_PATTERN || type == ExtendedColor.TYPE_SHADING)
2919            throw new RuntimeException(MessageLocalization.getComposedMessage("an.uncolored.tile.pattern.can.not.have.another.pattern.or.shading.as.color"));
2920        try {
2921            switch (type) {
2922                case ExtendedColor.TYPE_RGB:
2923                    if (patternColorspaceRGB == null) {
2924                        patternColorspaceRGB = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null);
2925                        PdfArray array = new PdfArray(PdfName.PATTERN);
2926                        array.add(PdfName.DEVICERGB);
2927                        addToBody(array, patternColorspaceRGB.getIndirectReference());
2928                    }
2929                    return patternColorspaceRGB;
2930                case ExtendedColor.TYPE_CMYK:
2931                    if (patternColorspaceCMYK == null) {
2932                        patternColorspaceCMYK = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null);
2933                        PdfArray array = new PdfArray(PdfName.PATTERN);
2934                        array.add(PdfName.DEVICECMYK);
2935                        addToBody(array, patternColorspaceCMYK.getIndirectReference());
2936                    }
2937                    return patternColorspaceCMYK;
2938                case ExtendedColor.TYPE_GRAY:
2939                    if (patternColorspaceGRAY == null) {
2940                        patternColorspaceGRAY = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null);
2941                        PdfArray array = new PdfArray(PdfName.PATTERN);
2942                        array.add(PdfName.DEVICEGRAY);
2943                        addToBody(array, patternColorspaceGRAY.getIndirectReference());
2944                    }
2945                    return patternColorspaceGRAY;
2946                case ExtendedColor.TYPE_SEPARATION: {
2947                    ColorDetails details = addSimple(((SpotColor)color).getPdfSpotColor());
2948                    ColorDetails patternDetails = documentSpotPatterns.get(details);
2949                    if (patternDetails == null) {
2950                        patternDetails = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null);
2951                        PdfArray array = new PdfArray(PdfName.PATTERN);
2952                        array.add(details.getIndirectReference());
2953                        addToBody(array, patternDetails.getIndirectReference());
2954                        documentSpotPatterns.put(details, patternDetails);
2955                    }
2956                    return patternDetails;
2957                }
2958                default:
2959                    throw new RuntimeException(MessageLocalization.getComposedMessage("invalid.color.type"));
2960            }
2961        }
2962        catch (Exception e) {
2963            throw new RuntimeException(e.getMessage());
2964        }
2965    }
2966
2967//  [M3] Images
2968
2969    /**
2970     * Use this method to get the strictImageSequence status.
2971     * @return value of property strictImageSequence
2972     */
2973    public boolean isStrictImageSequence() {
2974        return pdf.isStrictImageSequence();
2975    }
2976
2977    /**
2978     * Use this method to set the image sequence, so that it follows
2979     * the text in strict order (or not).
2980     * @param strictImageSequence new value of property strictImageSequence
2981     *
2982     */
2983    public void setStrictImageSequence(final boolean strictImageSequence) {
2984        pdf.setStrictImageSequence(strictImageSequence);
2985    }
2986
2987    /**
2988     * Use this method to clear text wrapping around images (if applicable).
2989     * @throws DocumentException
2990     */
2991    public void clearTextWrap() throws DocumentException {
2992        pdf.clearTextWrap();
2993    }
2994
2995    /** Dictionary, containing all the images of the PDF document */
2996    protected PdfDictionary imageDictionary = new PdfDictionary();
2997
2998    /** This is the list with all the images in the document. */
2999    private final HashMap<Long, PdfName> images = new HashMap<Long, PdfName>();
3000
3001    /**
3002     * Use this method to adds an image to the document
3003     * but not to the page resources. It is used with
3004     * templates and <CODE>Document.add(Image)</CODE>.
3005     * Use this method only if you know what you're doing!
3006     * @param image the <CODE>Image</CODE> to add
3007     * @return the name of the image added
3008     * @throws PdfException on error
3009     * @throws DocumentException on error
3010     */
3011    public PdfName addDirectImageSimple(final Image image) throws PdfException, DocumentException {
3012        return addDirectImageSimple(image, null);
3013    }
3014
3015    /**
3016     * Adds an image to the document but not to the page resources.
3017     * It is used with templates and <CODE>Document.add(Image)</CODE>.
3018     * Use this method only if you know what you're doing!
3019     * @param image the <CODE>Image</CODE> to add
3020     * @param fixedRef the reference to used. It may be <CODE>null</CODE>,
3021     * a <CODE>PdfIndirectReference</CODE> or a <CODE>PRIndirectReference</CODE>.
3022     * @return the name of the image added
3023     * @throws PdfException on error
3024     * @throws DocumentException on error
3025     */
3026    public PdfName addDirectImageSimple(final Image image, final PdfIndirectReference fixedRef) throws PdfException, DocumentException {
3027        PdfName name;
3028        // if the images is already added, just retrieve the name
3029        if (images.containsKey(image.getMySerialId())) {
3030            name = images.get(image.getMySerialId());
3031        }
3032        // if it's a new image, add it to the document
3033        else {
3034            if (image.isImgTemplate()) {
3035                name = new PdfName("img" + images.size());
3036                if(image instanceof ImgWMF){
3037                    try {
3038                        ImgWMF wmf = (ImgWMF)image;
3039                        wmf.readWMF(PdfTemplate.createTemplate(this, 0, 0));
3040                    }
3041                    catch (Exception e) {
3042                        throw new DocumentException(e);
3043                    }
3044                }
3045            }
3046            else {
3047                PdfIndirectReference dref = image.getDirectReference();
3048                if (dref != null) {
3049                    PdfName rname = new PdfName("img" + images.size());
3050                    images.put(image.getMySerialId(), rname);
3051                    imageDictionary.put(rname, dref);
3052                    return rname;
3053                }
3054                Image maskImage = image.getImageMask();
3055                PdfIndirectReference maskRef = null;
3056                if (maskImage != null) {
3057                    PdfName mname = images.get(maskImage.getMySerialId());
3058                    maskRef = getImageReference(mname);
3059                }
3060                PdfImage i = new PdfImage(image, "img" + images.size(), maskRef);
3061                if (image instanceof ImgJBIG2) {
3062                    byte[] globals = ((ImgJBIG2) image).getGlobalBytes();
3063                    if (globals != null) {
3064                        PdfDictionary decodeparms = new PdfDictionary();
3065                        decodeparms.put(PdfName.JBIG2GLOBALS, getReferenceJBIG2Globals(globals));
3066                        i.put(PdfName.DECODEPARMS, decodeparms);
3067                    }
3068                }
3069                if (image.hasICCProfile()) {
3070                    PdfICCBased icc = new PdfICCBased(image.getICCProfile(), image.getCompressionLevel());
3071                    PdfIndirectReference iccRef = add(icc);
3072                    PdfArray iccArray = new PdfArray();
3073                    iccArray.add(PdfName.ICCBASED);
3074                    iccArray.add(iccRef);
3075                    PdfArray colorspace = i.getAsArray(PdfName.COLORSPACE);
3076                    if (colorspace != null) {
3077                        if (colorspace.size() > 1 && PdfName.INDEXED.equals(colorspace.getPdfObject(0)))
3078                            colorspace.set(1, iccArray);
3079                        else
3080                            i.put(PdfName.COLORSPACE, iccArray);
3081                    }
3082                    else
3083                        i.put(PdfName.COLORSPACE, iccArray);
3084                }
3085                add(i, fixedRef);
3086                name = i.name();
3087            }
3088            images.put(image.getMySerialId(), name);
3089        }
3090        return name;
3091    }
3092
3093    /**
3094     * Writes a <CODE>PdfImage</CODE> to the outputstream.
3095     *
3096     * @param pdfImage the image to be added
3097     * @param fixedRef the IndirectReference, may be null then a new indirect reference is returned
3098     * @return a <CODE>PdfIndirectReference</CODE> to the encapsulated image
3099     * @throws PdfException when a document isn't open yet, or has been closed
3100     */
3101
3102    PdfIndirectReference add(final PdfImage pdfImage, PdfIndirectReference fixedRef) throws PdfException {
3103        if (! imageDictionary.contains(pdfImage.name())) {
3104            PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_IMAGE, pdfImage);
3105            if (fixedRef instanceof PRIndirectReference) {
3106                PRIndirectReference r2 = (PRIndirectReference)fixedRef;
3107                fixedRef = new PdfIndirectReference(0, getNewObjectNumber(r2.getReader(), r2.getNumber(), r2.getGeneration()));
3108            }
3109            try {
3110                if (fixedRef == null)
3111                    fixedRef = addToBody(pdfImage).getIndirectReference();
3112                else
3113                    addToBody(pdfImage, fixedRef);
3114            }
3115            catch(IOException ioe) {
3116                throw new ExceptionConverter(ioe);
3117            }
3118            imageDictionary.put(pdfImage.name(), fixedRef);
3119            return fixedRef;
3120        }
3121        return (PdfIndirectReference) imageDictionary.get(pdfImage.name());
3122    }
3123
3124    /**
3125     * return the <CODE>PdfIndirectReference</CODE> to the image with a given name.
3126     *
3127     * @param name the name of the image
3128     * @return a <CODE>PdfIndirectReference</CODE>
3129     */
3130
3131    PdfIndirectReference getImageReference(final PdfName name) {
3132        return (PdfIndirectReference) imageDictionary.get(name);
3133    }
3134
3135    protected PdfIndirectReference add(final PdfICCBased icc) {
3136        PdfIndirectObject object;
3137        try {
3138            object = addToBody(icc);
3139        }
3140        catch(IOException ioe) {
3141            throw new ExceptionConverter(ioe);
3142        }
3143        return object.getIndirectReference();
3144    }
3145
3146    /**
3147     * A HashSet with Stream objects containing JBIG2 Globals
3148     * @since 2.1.5
3149     */
3150    protected HashMap<PdfStream, PdfIndirectReference> JBIG2Globals = new HashMap<PdfStream, PdfIndirectReference>();
3151    /**
3152     * Gets an indirect reference to a JBIG2 Globals stream.
3153     * Adds the stream if it hasn't already been added to the writer.
3154         * @param       content a byte array that may already been added to the writer inside a stream object.
3155     * @return the PdfIndirectReference of the stream
3156     * @since  2.1.5
3157     */
3158    protected PdfIndirectReference getReferenceJBIG2Globals(final byte[] content) {
3159        if (content == null) return null;
3160        for (PdfStream stream : JBIG2Globals.keySet()) {
3161            if (Arrays.equals(content, stream.getBytes())) {
3162                return JBIG2Globals.get(stream);
3163            }
3164        }
3165        PdfStream stream = new PdfStream(content);
3166        PdfIndirectObject ref;
3167        try {
3168            ref = addToBody(stream);
3169        } catch (IOException e) {
3170            return null;
3171        }
3172        JBIG2Globals.put(stream, ref.getIndirectReference());
3173        return ref.getIndirectReference();
3174    }
3175
3176//  [F12] tagged PDF
3177    /**
3178     * A flag indicating the presence of structure elements that contain user properties attributes.
3179     */
3180    private boolean userProperties;
3181
3182    /**
3183     * Gets the flag indicating the presence of structure elements that contain user properties attributes.
3184     * @return the user properties flag
3185     */
3186    public boolean isUserProperties() {
3187        return this.userProperties;
3188    }
3189
3190    /**
3191     * Sets the flag indicating the presence of structure elements that contain user properties attributes.
3192     * @param userProperties the user properties flag
3193     */
3194    public void setUserProperties(final boolean userProperties) {
3195        this.userProperties = userProperties;
3196    }
3197
3198    /**
3199     * Holds value of property RGBTranparency.
3200     */
3201    private boolean rgbTransparencyBlending;
3202
3203    /**
3204     * Gets the transparency blending colorspace.
3205     * @return <code>true</code> if the transparency blending colorspace is RGB, <code>false</code>
3206     * if it is the default blending colorspace
3207     * @since 2.1.0
3208     */
3209    public boolean isRgbTransparencyBlending() {
3210        return this.rgbTransparencyBlending;
3211    }
3212
3213    /**
3214     * Sets the transparency blending colorspace to RGB. The default blending colorspace is
3215     * CMYK and will result in faded colors in the screen and in printing. Calling this method
3216     * will return the RGB colors to what is expected. The RGB blending will be applied to all subsequent pages
3217     * until other value is set.
3218     * Note that this is a generic solution that may not work in all cases.
3219     * @param rgbTransparencyBlending <code>true</code> to set the transparency blending colorspace to RGB, <code>false</code>
3220     * to use the default blending colorspace
3221     * @since 2.1.0
3222     */
3223    public void setRgbTransparencyBlending(final boolean rgbTransparencyBlending) {
3224        this.rgbTransparencyBlending = rgbTransparencyBlending;
3225    }
3226}