1 /*
2  * $Id: PdfWriter.java 3948 2009-06-03 15:17:22Z blowagie $
3  *
4  * Copyright 1999, 2000, 2001, 2002 Bruno Lowagie
5  *
6  * The contents of this file are subject to the Mozilla Public License Version 1.1
7  * (the "License"); you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at http://www.mozilla.org/MPL/
9  *
10  * Software distributed under the License is distributed on an "AS IS" basis,
11  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12  * for the specific language governing rights and limitations under the License.
13  *
14  * The Original Code is 'iText, a free JAVA-PDF library'.
15  *
16  * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
17  * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
18  * All Rights Reserved.
19  * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
20  * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
21  *
22  * Contributor(s): all the names of the contributors are added in the source code
23  * where applicable.
24  *
25  * Alternatively, the contents of this file may be used under the terms of the
26  * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
27  * provisions of LGPL are applicable instead of those above.  If you wish to
28  * allow use of your version of this file only under the terms of the LGPL
29  * License and not to allow others to use your version of this file under
30  * the MPL, indicate your decision by deleting the provisions above and
31  * replace them with the notice and other provisions required by the LGPL.
32  * If you do not delete the provisions above, a recipient may use your version
33  * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
34  *
35  * This library is free software; you can redistribute it and/or modify it
36  * under the terms of the MPL as stated above or under the terms of the GNU
37  * Library General Public License as published by the Free Software Foundation;
38  * either version 2 of the License, or any later version.
39  *
40  * This library is distributed in the hope that it will be useful, but WITHOUT
41  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
42  * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
43  * details.
44  *
45  * If you didn't download this code from the following link, you should check if
46  * you aren't using an obsolete version:
47  * http://www.lowagie.com/iText/
48  */

49
50 package com.lowagie.text.pdf;
51
52 import java.awt.Color;
53 import java.awt.color.ICC_Profile;
54 import java.io.ByteArrayOutputStream;
55 import java.io.IOException;
56 import java.io.OutputStream;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.LinkedHashMap;
60 import java.util.HashMap;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.Map;
64 import java.util.TreeMap;
65 import java.util.TreeSet;
66 import java.security.cert.Certificate;
67
68 import com.lowagie.text.DocListener;
69 import com.lowagie.text.DocWriter;
70 import com.lowagie.text.Document;
71 import com.lowagie.text.DocumentException;
72 import com.lowagie.text.ExceptionConverter;
73 import com.lowagie.text.Image;
74 import com.lowagie.text.ImgJBIG2;
75 import com.lowagie.text.ImgWMF;
76 import com.lowagie.text.Rectangle;
77 import com.lowagie.text.Table;
78 import com.lowagie.text.pdf.collection.PdfCollection;
79 import com.lowagie.text.pdf.events.PdfPageEventForwarder;
80 import com.lowagie.text.pdf.interfaces.PdfAnnotations;
81 import com.lowagie.text.pdf.interfaces.PdfDocumentActions;
82 import com.lowagie.text.pdf.interfaces.PdfEncryptionSettings;
83 import com.lowagie.text.pdf.interfaces.PdfPageActions;
84 import com.lowagie.text.pdf.interfaces.PdfVersion;
85 import com.lowagie.text.pdf.interfaces.PdfViewerPreferences;
86 import com.lowagie.text.pdf.interfaces.PdfXConformance;
87 import com.lowagie.text.pdf.interfaces.PdfRunDirection;
88 import com.lowagie.text.pdf.internal.PdfVersionImp;
89 import com.lowagie.text.pdf.internal.PdfXConformanceImp;
90 import com.lowagie.text.xml.xmp.XmpWriter;
91
92 /**
93  * A <CODE>DocWriter</CODE> class for PDF.
94  * <P>
95  * When this <CODE>PdfWriter</CODE> is added
96  * to a certain <CODE>PdfDocument</CODE>, the PDF representation of every Element
97  * added to this Document will be written to the outputstream.</P>
98  */

99
100 public class PdfWriter extends DocWriter implements
101     PdfViewerPreferences,
102     PdfEncryptionSettings,
103     PdfVersion,
104     PdfDocumentActions,
105     PdfPageActions,
106     PdfXConformance,
107     PdfRunDirection,
108     PdfAnnotations {
109
110     /**
111      * The highest generation number possible.
112      * @since    iText 2.1.6
113      */

114     public static final int GENERATION_MAX = 65535;
115     
116 // INNER CLASSES
117
118     /**
119      * This class generates the structure of a PDF document.
120      * <P>
121      * This class covers the third section of Chapter 5 in the 'Portable Document Format
122      * Reference Manual version 1.3' (page 55-60). It contains the body of a PDF document
123      * (section 5.14) and it can also generate a Cross-reference Table (section 5.15).
124      *
125      * @see        PdfWriter
126      * @see        PdfObject
127      * @see        PdfIndirectObject
128      */

129
130     public static class PdfBody {
131
132         // inner classes
133
134         /**
135          * <CODE>PdfCrossReference</CODE> is an entry in the PDF Cross-Reference table.
136          */

137
138         static class PdfCrossReference implements Comparable {
139
140             // membervariables
141             private int type;
142
143             /**    Byte offset in the PDF file. */
144             private int offset;
145
146             private int refnum;
147             /**    generation of the object. */
148             private int generation;
149
150             // constructors
151             /**
152              * Constructs a cross-reference element for a PdfIndirectObject.
153              * @param refnum
154              * @param    offset        byte offset of the object
155              * @param    generation    generation number of the object
156              */

157
158             PdfCrossReference(int refnum, int offset, int generation) {
159                 type = 0;
160                 this.offset = offset;
161                 this.refnum = refnum;
162                 this.generation = generation;
163             }
164
165             /**
166              * Constructs a cross-reference element for a PdfIndirectObject.
167              * @param refnum
168              * @param    offset        byte offset of the object
169              */

170
171             PdfCrossReference(int refnum, int offset) {
172                 type = 1;
173                 this.offset = offset;
174                 this.refnum = refnum;
175                 this.generation = 0;
176             }
177
178             PdfCrossReference(int type, int refnum, int offset, int generation) {
179                 this.type = type;
180                 this.offset = offset;
181                 this.refnum = refnum;
182                 this.generation = generation;
183             }
184
185             int getRefnum() {
186                 return refnum;
187             }
188
189             /**
190              * Returns the PDF representation of this <CODE>PdfObject</CODE>.
191              * @param os
192              * @throws IOException
193              */

194
195             public void toPdf(OutputStream os) throws IOException {
196                 StringBuffer off = new StringBuffer("0000000000").append(offset);
197                 off.delete(0, off.length() - 10);
198                 StringBuffer gen = new StringBuffer("00000").append(generation);
199                 gen.delete(0, gen.length() - 5);
200
201                 off.append(' ').append(gen).append(generation == GENERATION_MAX ? " f \n" : " n \n");
202                 os.write(getISOBytes(off.toString()));
203             }
204
205             /**
206              * Writes PDF syntax to the OutputStream
207              * @param midSize
208              * @param os
209              * @throws IOException
210              */

211             public void toPdf(int midSize, OutputStream os) throws IOException {
212                 os.write((byte)type);
213                 while (--midSize >= 0)
214                     os.write((byte)((offset >>> (8 * midSize)) & 0xff));
215                 os.write((byte)((generation >>> 8) & 0xff));
216                 os.write((byte)(generation & 0xff));
217             }
218
219             /**
220              * @see java.lang.Comparable#compareTo(java.lang.Object)
221              */

222             public int compareTo(Object o) {
223                 PdfCrossReference other = (PdfCrossReference)o;
224                 return (refnum < other.refnum ? -1 : (refnum==other.refnum ? 0 : 1));
225             }
226
227             /**
228              * @see java.lang.Object#equals(java.lang.Object)
229              */

230             public boolean equals(Object obj) {
231                 if (obj instanceof PdfCrossReference) {
232                     PdfCrossReference other = (PdfCrossReference)obj;
233                     return (refnum == other.refnum);
234                 }
235                 else
236                     return false;
237             }
238
239             /**
240              * @see java.lang.Object#hashCode()
241              */

242             public int hashCode() {
243                 return refnum;
244             }
245
246         }
247
248         private static final int OBJSINSTREAM = 200;
249
250         // membervariables
251
252         /** array containing the cross-reference table of the normal objects. */
253         private TreeSet xrefs;
254         private int refnum;
255         /** the current byte position in the body. */
256         private int position;
257         private PdfWriter writer;
258         private ByteBuffer index;
259         private ByteBuffer streamObjects;
260         private int currentObjNum;
261         private int numObj = 0;
262
263         // constructors
264
265         /**
266          * Constructs a new <CODE>PdfBody</CODE>.
267          * @param writer
268          */

269         PdfBody(PdfWriter writer) {
270             xrefs = new TreeSet();
271             xrefs.add(new PdfCrossReference(0, 0, GENERATION_MAX));
272             position = writer.getOs().getCounter();
273             refnum = 1;
274             this.writer = writer;
275         }
276
277         // methods
278
279         void setRefnum(int refnum) {
280             this.refnum = refnum;
281         }
282
283         private PdfWriter.PdfBody.PdfCrossReference addToObjStm(PdfObject obj, int nObj) throws IOException {
284             if (numObj >= OBJSINSTREAM)
285                 flushObjStm();
286             if (index == null) {
287                 index = new ByteBuffer();
288                 streamObjects = new ByteBuffer();
289                 currentObjNum = getIndirectReferenceNumber();
290                 numObj = 0;
291             }
292             int p = streamObjects.size();
293             int idx = numObj++;
294             PdfEncryption enc = writer.crypto;
295             writer.crypto = null;
296             obj.toPdf(writer, streamObjects);
297             writer.crypto = enc;
298             streamObjects.append(' ');
299             index.append(nObj).append(' ').append(p).append(' ');
300             return new PdfWriter.PdfBody.PdfCrossReference(2, nObj, currentObjNum, idx);
301         }
302
303         private void flushObjStm() throws IOException {
304             if (numObj == 0)
305                 return;
306             int first = index.size();
307             index.append(streamObjects);
308             PdfStream stream = new PdfStream(index.toByteArray());
309             stream.flateCompress(writer.getCompressionLevel());
310             stream.put(PdfName.TYPE, PdfName.OBJSTM);
311             stream.put(PdfName.N, new PdfNumber(numObj));
312             stream.put(PdfName.FIRST, new PdfNumber(first));
313             add(stream, currentObjNum);
314             index = null;
315             streamObjects = null;
316             numObj = 0;
317         }
318
319         /**
320          * Adds a <CODE>PdfObject</CODE> to the body.
321          * <P>
322          * This methods creates a <CODE>PdfIndirectObject</CODE> with a
323          * certain number, containing the given <CODE>PdfObject</CODE>.
324          * It also adds a <CODE>PdfCrossReference</CODE> for this object
325          * to an <CODE>ArrayList</CODE> that will be used to build the
326          * Cross-reference Table.
327          *
328          * @param        object            a <CODE>PdfObject</CODE>
329          * @return        a <CODE>PdfIndirectObject</CODE>
330          * @throws IOException
331          */

332
333         PdfIndirectObject add(PdfObject object) throws IOException {
334             return add(object, getIndirectReferenceNumber());
335         }
336
337         PdfIndirectObject add(PdfObject object, boolean inObjStm) throws IOException {
338             return add(object, getIndirectReferenceNumber(), inObjStm);
339         }
340
341         /**
342          * Gets a PdfIndirectReference for an object that will be created in the future.
343          * @return a PdfIndirectReference
344          */

345
346         PdfIndirectReference getPdfIndirectReference() {
347             return new PdfIndirectReference(0, getIndirectReferenceNumber());
348         }
349
350         int getIndirectReferenceNumber() {
351             int n = refnum++;
352             xrefs.add(new PdfCrossReference(n, 0, GENERATION_MAX));
353             return n;
354         }
355
356         /**
357          * Adds a <CODE>PdfObject</CODE> to the body given an already existing
358          * PdfIndirectReference.
359          * <P>
360          * This methods creates a <CODE>PdfIndirectObject</CODE> with the number given by
361          * <CODE>ref</CODE>, containing the given <CODE>PdfObject</CODE>.
362          * It also adds a <CODE>PdfCrossReference</CODE> for this object
363          * to an <CODE>ArrayList</CODE> that will be used to build the
364          * Cross-reference Table.
365          *
366          * @param        object            a <CODE>PdfObject</CODE>
367          * @param        ref                a <CODE>PdfIndirectReference</CODE>
368          * @return        a <CODE>PdfIndirectObject</CODE>
369          * @throws IOException
370          */

371
372         PdfIndirectObject add(PdfObject object, PdfIndirectReference ref) throws IOException {
373             return add(object, ref.getNumber());
374         }
375
376         PdfIndirectObject add(PdfObject object, PdfIndirectReference ref, boolean inObjStm) throws IOException {
377             return add(object, ref.getNumber(), inObjStm);
378         }
379
380         PdfIndirectObject add(PdfObject object, int refNumber) throws IOException {
381             return add(object, refNumber, true); // to false
382         }
383
384         PdfIndirectObject add(PdfObject object, int refNumber, boolean inObjStm) throws IOException {
385             if (inObjStm && object.canBeInObjStm() && writer.isFullCompression()) {
386                 PdfCrossReference pxref = addToObjStm(object, refNumber);
387                 PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer);
388                 if (!xrefs.add(pxref)) {
389                     xrefs.remove(pxref);
390                     xrefs.add(pxref);
391                 }
392                 return indirect;
393             }
394             else {
395                 PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer);
396                 PdfCrossReference pxref = new PdfCrossReference(refNumber, position);
397                 if (!xrefs.add(pxref)) {
398                     xrefs.remove(pxref);
399                     xrefs.add(pxref);
400                 }
401                 indirect.writeTo(writer.getOs());
402                 position = writer.getOs().getCounter();
403                 return indirect;
404             }
405         }
406
407         /**
408          * Returns the offset of the Cross-Reference table.
409          *
410          * @return        an offset
411          */

412
413         int offset() {
414             return position;
415         }
416
417         /**
418          * Returns the total number of objects contained in the CrossReferenceTable of this <CODE>Body</CODE>.
419          *
420          * @return    a number of objects
421          */

422
423         int size() {
424             return Math.max(((PdfCrossReference)xrefs.last()).getRefnum() + 1, refnum);
425         }
426
427         /**
428          * Returns the CrossReferenceTable of the <CODE>Body</CODE>.
429          * @param os
430          * @param root
431          * @param info
432          * @param encryption
433          * @param fileID
434          * @param prevxref
435          * @throws IOException
436          */

437
438         void writeCrossReferenceTable(OutputStream os, PdfIndirectReference root, PdfIndirectReference info, PdfIndirectReference encryption, PdfObject fileID, int prevxref) throws IOException {
439             int refNumber = 0;
440             if (writer.isFullCompression()) {
441                 flushObjStm();
442                 refNumber = getIndirectReferenceNumber();
443                 xrefs.add(new PdfCrossReference(refNumber, position));
444             }
445             PdfCrossReference entry = (PdfCrossReference)xrefs.first();
446             int first = entry.getRefnum();
447             int len = 0;
448             ArrayList sections = new ArrayList();
449             for (Iterator i = xrefs.iterator(); i.hasNext(); ) {
450                 entry = (PdfCrossReference)i.next();
451                 if (first + len == entry.getRefnum())
452                     ++len;
453                 else {
454                     sections.add(new Integer(first));
455                     sections.add(new Integer(len));
456                     first = entry.getRefnum();
457                     len = 1;
458                 }
459             }
460             sections.add(new Integer(first));
461             sections.add(new Integer(len));
462             if (writer.isFullCompression()) {
463                 int mid = 4;
464                 int mask = 0xff000000;
465                 for (; mid > 1; --mid) {
466                     if ((mask & position) != 0)
467                         break;
468                     mask >>>= 8;
469                 }
470                 ByteBuffer buf = new ByteBuffer();
471
472                 for (Iterator i = xrefs.iterator(); i.hasNext(); ) {
473                     entry = (PdfCrossReference) i.next();
474                     entry.toPdf(mid, buf);
475                 }
476                 PdfStream xr = new PdfStream(buf.toByteArray());
477                 buf = null;
478                 xr.flateCompress(writer.getCompressionLevel());
479                 xr.put(PdfName.SIZE, new PdfNumber(size()));
480                 xr.put(PdfName.ROOT, root);
481                 if (info != null) {
482                     xr.put(PdfName.INFO, info);
483                 }
484                 if (encryption != null)
485                     xr.put(PdfName.ENCRYPT, encryption);
486                 if (fileID != null)
487                     xr.put(PdfName.ID, fileID);
488                 xr.put(PdfName.W, new PdfArray(new int[]{1, mid, 2}));
489                 xr.put(PdfName.TYPE, PdfName.XREF);
490                 PdfArray idx = new PdfArray();
491                 for (int k = 0; k < sections.size(); ++k)
492                     idx.add(new PdfNumber(((Integer)sections.get(k)).intValue()));
493                 xr.put(PdfName.INDEX, idx);
494                 if (prevxref > 0)
495                     xr.put(PdfName.PREV, new PdfNumber(prevxref));
496                 PdfEncryption enc = writer.crypto;
497                 writer.crypto = null;
498                 PdfIndirectObject indirect = new PdfIndirectObject(refNumber, xr, writer);
499                 indirect.writeTo(writer.getOs());
500                 writer.crypto = enc;
501             }
502             else {
503                 os.write(getISOBytes("xref\n"));
504                 Iterator i = xrefs.iterator();
505                 for (int k = 0; k < sections.size(); k += 2) {
506                     first = ((Integer)sections.get(k)).intValue();
507                     len = ((Integer)sections.get(k + 1)).intValue();
508                     os.write(getISOBytes(String.valueOf(first)));
509                     os.write(getISOBytes(" "));
510                     os.write(getISOBytes(String.valueOf(len)));
511                     os.write('\n');
512                     while (len-- > 0) {
513                         entry = (PdfCrossReference) i.next();
514                         entry.toPdf(os);
515                     }
516                 }
517             }
518         }
519     }
520
521     /**
522      * <CODE>PdfTrailer</CODE> is the PDF Trailer object.
523      * <P>
524      * This object is described in the 'Portable Document Format Reference Manual version 1.3'
525      * section 5.16 (page 59-60).
526      */

527
528     static class PdfTrailer extends PdfDictionary {
529
530         // membervariables
531
532         int offset;
533
534         // constructors
535
536         /**
537          * Constructs a PDF-Trailer.
538          *
539          * @param        size        the number of entries in the <CODE>PdfCrossReferenceTable</CODE>
540          * @param        offset        offset of the <CODE>PdfCrossReferenceTable</CODE>
541          * @param        root        an indirect reference to the root of the PDF document
542          * @param        info        an indirect reference to the info object of the PDF document
543          * @param encryption
544          * @param fileID
545          * @param prevxref
546          */

547
548         PdfTrailer(int size, int offset, PdfIndirectReference root, PdfIndirectReference info, PdfIndirectReference encryption, PdfObject fileID, int prevxref) {
549             this.offset = offset;
550             put(PdfName.SIZE, new PdfNumber(size));
551             put(PdfName.ROOT, root);
552             if (info != null) {
553                 put(PdfName.INFO, info);
554             }
555             if (encryption != null)
556                 put(PdfName.ENCRYPT, encryption);
557             if (fileID != null)
558                 put(PdfName.ID, fileID);
559             if (prevxref > 0)
560                 put(PdfName.PREV, new PdfNumber(prevxref));
561         }
562
563         /**
564          * Returns the PDF representation of this <CODE>PdfObject</CODE>.
565          * @param writer
566          * @param os
567          * @throws IOException
568          */

569         public void toPdf(PdfWriter writer, OutputStream os) throws IOException {
570             os.write(getISOBytes("trailer\n"));
571             super.toPdf(null, os);
572             os.write(getISOBytes("\nstartxref\n"));
573             os.write(getISOBytes(String.valueOf(offset)));
574             os.write(getISOBytes("\n%%EOF\n"));
575         }
576     }
577
578 //    ESSENTIALS
579
580 //    Construct a PdfWriter instance
581
582     /**
583      * Constructs a <CODE>PdfWriter</CODE>.
584      */

585     protected PdfWriter() {
586     }
587
588     /**
589      * Constructs a <CODE>PdfWriter</CODE>.
590      * <P>
591      * Remark: a PdfWriter can only be constructed by calling the method
592      * <CODE>getInstance(Document document, OutputStream os)</CODE>.
593      *
594      * @param    document    The <CODE>PdfDocument</CODE> that has to be written
595      * @param    os            The <CODE>OutputStream</CODE> the writer has to write to.
596      */

597
598     protected PdfWriter(PdfDocument document, OutputStream os) {
599         super(document, os);
600         pdf = document;
601         directContent = new PdfContentByte(this);
602         directContentUnder = new PdfContentByte(this);
603     }
604
605     /**
606      * Use this method to get an instance of the <CODE>PdfWriter</CODE>.
607      *
608      * @param    document    The <CODE>Document</CODE> that has to be written
609      * @param    os    The <CODE>OutputStream</CODE> the writer has to write to.
610      * @return    a new <CODE>PdfWriter</CODE>
611      *
612      * @throws    DocumentException on error
613      */

614
615     public static PdfWriter getInstance(Document document, OutputStream os)
616     throws DocumentException {
617         PdfDocument pdf = new PdfDocument();
618         document.addDocListener(pdf);
619         PdfWriter writer = new PdfWriter(pdf, os);
620         pdf.addWriter(writer);
621         return writer;
622     }
623
624     /**
625      * Use this method to get an instance of the <CODE>PdfWriter</CODE>.
626      *
627      * @return a new <CODE>PdfWriter</CODE>
628      * @param document The <CODE>Document</CODE> that has to be written
629      * @param os The <CODE>OutputStream</CODE> the writer has to write to.
630      * @param listener A <CODE>DocListener</CODE> to pass to the PdfDocument.
631      * @throws DocumentException on error
632      */

633
634     public static PdfWriter getInstance(Document document, OutputStream os, DocListener listener)
635     throws DocumentException {
636         PdfDocument pdf = new PdfDocument();
637         pdf.addDocListener(listener);
638         document.addDocListener(pdf);
639         PdfWriter writer = new PdfWriter(pdf, os);
640         pdf.addWriter(writer);
641         return writer;
642     }
643
644 //    the PdfDocument instance
645
646     /** the pdfdocument object. */
647     protected PdfDocument pdf;
648
649     /**
650      * Gets the <CODE>PdfDocument</CODE> associated with this writer.
651      * @return the <CODE>PdfDocument</CODE>
652      */

653
654     PdfDocument getPdfDocument() {
655         return pdf;
656     }
657
658     /**
659      * Use this method to get the info dictionary if you want to
660      * change it directly (add keys and values to the info dictionary).
661      * @return the info dictionary
662      */

663     public PdfDictionary getInfo() {
664         return pdf.getInfo();
665     }
666
667     /**
668      * Use this method to get the current vertical page position.
669      * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects
670      *   for elements that do not terminate the lines they've started because those lines will get
671      *   terminated.
672      * @return The current vertical page position.
673      */

674     public float getVerticalPosition(boolean ensureNewLine) {
675         return pdf.getVerticalPosition(ensureNewLine);
676     }
677
678     /**
679      * Sets the initial leading for the PDF document.
680      * This has to be done before the document is opened.
681      * @param    leading    the initial leading
682      * @since    2.1.6
683      * @throws    DocumentException    if you try setting the leading after the document was opened.
684      */

685     public void setInitialLeading(float leading) throws DocumentException {
686         if (open)
687             throw new DocumentException("You can't set the initial leading if the document is already open.");
688         pdf.setLeading(leading);
689     }
690     
691 //    the PdfDirectContentByte instances
692
693 /*
694  * You should see Direct Content as a canvas on which you can draw
695  * graphics and text. One canvas goes on top of the page (getDirectContent),
696  * the other goes underneath (getDirectContentUnder).
697  * You can always the same object throughout your document,
698  * even if you have moved to a new page. Whatever you add on
699  * the canvas will be displayed on top or under the current page.
700  */

701
702     /** The direct content in this document. */
703     protected PdfContentByte directContent;
704
705     /** The direct content under in this document. */
706     protected PdfContentByte directContentUnder;
707
708     /**
709      * Use this method to get the direct content for this document.
710      * There is only one direct content, multiple calls to this method
711      * will allways retrieve the same object.
712      * @return the direct content
713      */

714
715     public PdfContentByte getDirectContent() {
716         if (!open)
717             throw new RuntimeException("The document is not open.");
718         return directContent;
719     }
720
721     /**
722      * Use this method to get the direct content under for this document.
723      * There is only one direct content, multiple calls to this method
724      * will always retrieve the same object.
725      * @return the direct content
726      */

727
728     public PdfContentByte getDirectContentUnder() {
729         if (!open)
730             throw new RuntimeException("The document is not open.");
731         return directContentUnder;
732     }
733
734     /**
735      * Resets all the direct contents to empty.
736      * This happens when a new page is started.
737      */

738     void resetContent() {
739         directContent.reset();
740         directContentUnder.reset();
741     }
742
743 //    PDF body
744
745 /*
746  * A PDF file has 4 parts: a header, a body, a cross-reference table, and a trailer.
747  * The body contains all the PDF objects that make up the PDF document.
748  * Each element gets a reference (a set of numbers) and the byte position of
749  * every object is stored in the cross-reference table.
750  * Use these methods only if you know what you're doing.
751  */

752
753     /** body of the PDF document */
754     protected PdfBody body;
755
756     /**
757      * Adds the local destinations to the body of the document.
758      * @param dest the <CODE>HashMap</CODE> containing the destinations
759      * @throws IOException on error
760      */

761
762     void addLocalDestinations(TreeMap dest) throws IOException {
763         for (Iterator i = dest.entrySet().iterator(); i.hasNext();) {
764             Map.Entry entry = (Map.Entry) i.next();
765             String name = (String) entry.getKey();
766             Object obj[] = (Object[]) entry.getValue();
767             PdfDestination destination = (PdfDestination)obj[2];
768             if (obj[1] == null)
769                 obj[1] = getPdfIndirectReference();
770             if (destination == null)
771                 addToBody(new PdfString("invalid_" + name), (PdfIndirectReference)obj[1]);
772             else
773                 addToBody(destination, (PdfIndirectReference)obj[1]);
774         }
775     }
776
777     /**
778      * Use this method to add a PDF object to the PDF body.
779      * Use this method only if you know what you're doing!
780      * @param object
781      * @return a PdfIndirectObject
782      * @throws IOException
783      */

784     public PdfIndirectObject addToBody(PdfObject object) throws IOException {
785         PdfIndirectObject iobj = body.add(object);
786         return iobj;
787     }
788
789     /**
790      * Use this method to add a PDF object to the PDF body.
791      * Use this method only if you know what you're doing!
792      * @param object
793      * @param inObjStm
794      * @return a PdfIndirectObject
795      * @throws IOException
796      */

797     public PdfIndirectObject addToBody(PdfObject object, boolean inObjStm) throws IOException {
798         PdfIndirectObject iobj = body.add(object, inObjStm);
799         return iobj;
800     }
801
802     /**
803      * Use this method to add a PDF object to the PDF body.
804      * Use this method only if you know what you're doing!
805      * @param object
806      * @param ref
807      * @return a PdfIndirectObject
808      * @throws IOException
809      */

810     public PdfIndirectObject addToBody(PdfObject object, PdfIndirectReference ref) throws IOException {
811         PdfIndirectObject iobj = body.add(object, ref);
812         return iobj;
813     }
814
815     /**
816      * Use this method to add a PDF object to the PDF body.
817      * Use this method only if you know what you're doing!
818      * @param object
819      * @param ref
820      * @param inObjStm
821      * @return a PdfIndirectObject
822      * @throws IOException
823      */

824     public PdfIndirectObject addToBody(PdfObject object, PdfIndirectReference ref, boolean inObjStm) throws IOException {
825         PdfIndirectObject iobj = body.add(object, ref, inObjStm);
826         return iobj;
827     }
828
829     /**
830      * Use this method to add a PDF object to the PDF body.
831      * Use this method only if you know what you're doing!
832      * @param object
833      * @param refNumber
834      * @return a PdfIndirectObject
835      * @throws IOException
836      */

837     public PdfIndirectObject addToBody(PdfObject object, int refNumber) throws IOException {
838         PdfIndirectObject iobj = body.add(object, refNumber);
839         return iobj;
840     }
841
842     /**
843      * Use this method to add a PDF object to the PDF body.
844      * Use this method only if you know what you're doing!
845      * @param object
846      * @param refNumber
847      * @param inObjStm
848      * @return a PdfIndirectObject
849      * @throws IOException
850      */

851     public PdfIndirectObject addToBody(PdfObject object, int refNumber, boolean inObjStm) throws IOException {
852         PdfIndirectObject iobj = body.add(object, refNumber, inObjStm);
853         return iobj;
854     }
855
856     /**
857      * Use this to get an <CODE>PdfIndirectReference</CODE> for an object that
858      * will be created in the future.
859      * Use this method only if you know what you're doing!
860      * @return the <CODE>PdfIndirectReference</CODE>
861      */

862
863     public PdfIndirectReference getPdfIndirectReference() {
864         return body.getPdfIndirectReference();
865     }
866
867     int getIndirectReferenceNumber() {
868         return body.getIndirectReferenceNumber();
869     }
870
871     /**
872      * Returns the outputStreamCounter.
873      * @return the outputStreamCounter
874      */

875     OutputStreamCounter getOs() {
876         return os;
877     }
878
879
880 //    PDF Catalog
881
882 /*
883  * The Catalog is also called the root object of the document.
884  * Whereas the Cross-Reference maps the objects number with the
885  * byte offset so that the viewer can find the objects, the
886  * Catalog tells the viewer the numbers of the objects needed
887  * to render the document.
888  */

889
890     protected PdfDictionary getCatalog(PdfIndirectReference rootObj)
891     {
892         PdfDictionary catalog = pdf.getCatalog(rootObj);
893         // [F12] tagged PDF
894         if (tagged) {
895             try {
896                 getStructureTreeRoot().buildTree();
897             }
898             catch (Exception e) {
899                 throw new ExceptionConverter(e);
900             }
901             catalog.put(PdfName.STRUCTTREEROOT, structureTreeRoot.getReference());
902             PdfDictionary mi = new PdfDictionary();
903             mi.put(PdfName.MARKED, PdfBoolean.PDFTRUE);
904             if (userProperties)
905                 mi.put(PdfName.USERPROPERTIES, PdfBoolean.PDFTRUE);
906             catalog.put(PdfName.MARKINFO, mi);
907         }
908         // [F13] OCG
909         if (!documentOCG.isEmpty()) {
910             fillOCProperties(false);
911             catalog.put(PdfName.OCPROPERTIES, OCProperties);
912         }
913         return catalog;
914     }
915
916     /** Holds value of property extraCatalog this is used for Output Intents. */
917     protected PdfDictionary extraCatalog;
918
919     /**
920      * Sets extra keys to the catalog.
921      * @return the catalog to change
922      */

923     public PdfDictionary getExtraCatalog() {
924         if (extraCatalog == null)
925             extraCatalog = new PdfDictionary();
926         return this.extraCatalog;
927     }
928
929 //    PdfPages
930
931 /*
932  * The page root keeps the complete page tree of the document.
933  * There's an entry in the Catalog that refers to the root
934  * of the page tree, the page tree contains the references
935  * to pages and other page trees.
936  */

937
938     /** The root of the page tree. */
939     protected PdfPages root = new PdfPages(this);
940     /** The PdfIndirectReference to the pages. */
941     protected ArrayList pageReferences = new ArrayList();
942     /** The current page number. */
943     protected int currentPageNumber = 1;
944     /**
945      * The value of the Tabs entry in the page dictionary.
946      * @since    2.1.5
947      */

948     protected PdfName tabs = null;
949
950     /**
951      * Use this method to make sure the page tree has a linear structure
952      * (every leave is attached directly to the root).
953      * Use this method to allow page reordering with method reorderPages.
954      */

955      public void setLinearPageMode() {
956         root.setLinearMode(null);
957     }
958
959     /**
960      * Use this method to reorder the pages in the document.
961      * A <CODE>null</CODE> argument value only returns the number of pages to process.
962      * It is advisable to issue a <CODE>Document.newPage()</CODE> before using this method.
963      * @return the total number of pages
964      * @param order an array with the new page sequence. It must have the
965      * same size as the number of pages.
966      * @throws DocumentException if all the pages are not present in the array
967      */

968     public int reorderPages(int order[]) throws DocumentException {
969         return root.reorderPages(order);
970     }
971
972     /**
973      * Use this method to get a reference to a page existing or not.
974      * If the page does not exist yet the reference will be created
975      * in advance. If on closing the document, a page number greater
976      * than the total number of pages was requested, an exception
977      * is thrown.
978      * @param page the page number. The first page is 1
979      * @return the reference to the page
980      */

981     public PdfIndirectReference getPageReference(int page) {
982         --page;
983         if (page < 0)
984             throw new IndexOutOfBoundsException("The page numbers start at 1.");
985         PdfIndirectReference ref;
986         if (page < pageReferences.size()) {
987             ref = (PdfIndirectReference)pageReferences.get(page);
988             if (ref == null) {
989                 ref = body.getPdfIndirectReference();
990                 pageReferences.set(page, ref);
991             }
992         }
993         else {
994             int empty = page - pageReferences.size();
995             for (int k = 0; k < empty; ++k)
996                 pageReferences.add(null);
997             ref = body.getPdfIndirectReference();
998             pageReferences.add(ref);
999         }
1000         return ref;
1001     }
1002
1003     /**
1004      * Gets the pagenumber of this document.
1005      * This number can be different from the real pagenumber,
1006      * if you have (re)set the page number previously.
1007      * @return a page number
1008      */

1009
1010     public int getPageNumber() {
1011         return pdf.getPageNumber();
1012     }
1013
1014     PdfIndirectReference getCurrentPage() {
1015         return getPageReference(currentPageNumber);
1016     }
1017
1018     public int getCurrentPageNumber() {
1019         return currentPageNumber;
1020     }
1021
1022     /**
1023      * Sets the value for the Tabs entry in the page tree.
1024      * @param    tabs    Can be PdfName.R, PdfName.C or PdfName.S.
1025      * Since the Adobe Extensions Level 3, it can also be PdfName.A
1026      * or PdfName.W
1027      * @since    2.1.5
1028      */

1029     public void setTabs(PdfName tabs) {
1030         this.tabs = tabs;
1031     }
1032
1033     /**
1034      * Returns the value to be used for the Tabs entry in the page tree.
1035      * @since    2.1.5
1036      */

1037     public PdfName getTabs() {
1038         return tabs;
1039     }
1040
1041     /**
1042      * Adds some <CODE>PdfContents</CODE> to this Writer.
1043      * <P>
1044      * The document has to be open before you can begin to add content
1045      * to the body of the document.
1046      *
1047      * @return a <CODE>PdfIndirectReference</CODE>
1048      * @param page the <CODE>PdfPage</CODE> to add
1049      * @param contents the <CODE>PdfContents</CODE> of the page
1050      * @throws PdfException on error
1051      */

1052
1053     PdfIndirectReference add(PdfPage page, PdfContents contents) throws PdfException {
1054         if (!open) {
1055             throw new PdfException("The document isn't open.");
1056         }
1057         PdfIndirectObject object;
1058         try {
1059             object = addToBody(contents);
1060         }
1061         catch(IOException ioe) {
1062             throw new ExceptionConverter(ioe);
1063         }
1064         page.add(object.getIndirectReference());
1065         // [U5]
1066         if (group != null) {
1067             page.put(PdfName.GROUP, group);
1068             group = null;
1069         }
1070         else if (rgbTransparencyBlending) {
1071             PdfDictionary pp = new PdfDictionary();
1072             pp.put(PdfName.TYPE, PdfName.GROUP);
1073             pp.put(PdfName.S, PdfName.TRANSPARENCY);
1074             pp.put(PdfName.CS, PdfName.DEVICERGB);
1075             page.put(PdfName.GROUP, pp);
1076         }
1077         root.addPage(page);
1078         currentPageNumber++;
1079         return null;
1080     }
1081
1082 //    page events
1083
1084 /*
1085  * Page events are specific for iText, not for PDF.
1086  * Upon specific events (for instance when a page starts
1087  * or ends), the corresponding method in the page event
1088  * implementation that is added to the writer is invoked.
1089  */

1090
1091     /** The <CODE>PdfPageEvent</CODE> for this document. */
1092     private PdfPageEvent pageEvent;
1093
1094     /**
1095      * Sets the <CODE>PdfPageEvent</CODE> for this document.
1096      * @param event the <CODE>PdfPageEvent</CODE> for this document
1097      */

1098
1099     public void setPageEvent(PdfPageEvent event) {
1100         if (event == nullthis.pageEvent = null;
1101         else if (this.pageEvent == nullthis.pageEvent = event;
1102         else if (this.pageEvent instanceof PdfPageEventForwarder) ((PdfPageEventForwarder)this.pageEvent).addPageEvent(event);
1103         else {
1104             PdfPageEventForwarder forward = new PdfPageEventForwarder();
1105             forward.addPageEvent(this.pageEvent);
1106             forward.addPageEvent(event);
1107             this.pageEvent = forward;
1108         }
1109     }
1110
1111     /**
1112      * Gets the <CODE>PdfPageEvent</CODE> for this document or <CODE>null</CODE>
1113      * if none is set.
1114      * @return the <CODE>PdfPageEvent</CODE> for this document or <CODE>null</CODE>
1115      * if none is set
1116      */

1117
1118     public PdfPageEvent getPageEvent() {
1119         return pageEvent;
1120     }
1121
1122 //    Open and Close methods + method that create the PDF
1123
1124     /** A number referring to the previous Cross-Reference Table. */
1125     protected int prevxref = 0;
1126
1127     /**
1128      * Signals that the <CODE>Document</CODE> has been opened and that
1129      * <CODE>Elements</CODE> can be added.
1130      * <P>
1131      * When this method is called, the PDF-document header is
1132      * written to the outputstream.
1133      * @see com.lowagie.text.DocWriter#open()
1134      */

1135     public void open() {
1136         super.open();
1137         try {
1138             pdf_version.writeHeader(os);
1139             body = new PdfBody(this);
1140             if (pdfxConformance.isPdfX32002()) {
1141                 PdfDictionary sec = new PdfDictionary();
1142                 sec.put(PdfName.GAMMA, new PdfArray(new float[]{2.2f,2.2f,2.2f}));
1143                 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}));
1144                 sec.put(PdfName.WHITEPOINT, new PdfArray(new float[]{0.9505f,1f,1.089f}));
1145                 PdfArray arr = new PdfArray(PdfName.CALRGB);
1146                 arr.add(sec);
1147                 setDefaultColorspace(PdfName.DEFAULTRGB, addToBody(arr).getIndirectReference());
1148             }
1149         }
1150         catch(IOException ioe) {
1151             throw new ExceptionConverter(ioe);
1152         }
1153     }
1154
1155     /**
1156      * Signals that the <CODE>Document</CODE> was closed and that no other
1157      * <CODE>Elements</CODE> will be added.
1158      * <P>
1159      * The pages-tree is built and written to the outputstream.
1160      * A Catalog is constructed, as well as an Info-object,
1161      * the reference table is composed and everything is written
1162      * to the outputstream embedded in a Trailer.
1163      * @see com.lowagie.text.DocWriter#close()
1164      */

1165     public void close() {
1166         if (open) {
1167             if ((currentPageNumber - 1) != pageReferences.size())
1168                 throw new RuntimeException("The page " + pageReferences.size() +
1169                 " was requested but the document has only " + (currentPageNumber - 1) + " pages.");
1170             pdf.close();
1171             try {
1172                 addSharedObjectsToBody();
1173                 // add the root to the body
1174                 PdfIndirectReference rootRef = root.writePageTree();
1175                 // make the catalog-object and add it to the body
1176                 PdfDictionary catalog = getCatalog(rootRef);
1177                 // [C9] if there is XMP data to add: add it
1178                 if (xmpMetadata != null) {
1179                     PdfStream xmp = new PdfStream(xmpMetadata);
1180                     xmp.put(PdfName.TYPE, PdfName.METADATA);
1181                     xmp.put(PdfName.SUBTYPE, PdfName.XML);
1182                     if (crypto != null && !crypto.isMetadataEncrypted()) {
1183                         PdfArray ar = new PdfArray();
1184                         ar.add(PdfName.CRYPT);
1185                         xmp.put(PdfName.FILTER, ar);
1186                     }
1187                     catalog.put(PdfName.METADATA, body.add(xmp).getIndirectReference());
1188                 }
1189                 // [C10] make pdfx conformant
1190                 if (isPdfX()) {
1191                     pdfxConformance.completeInfoDictionary(getInfo());
1192                     pdfxConformance.completeExtraCatalog(getExtraCatalog());
1193                 }
1194                 // [C11] Output Intents
1195                 if (extraCatalog != null) {
1196                     catalog.mergeDifferent(extraCatalog);
1197                 }
1198
1199                 writeOutlines(catalog, false);
1200
1201                 // add the Catalog to the body
1202                 PdfIndirectObject indirectCatalog = addToBody(catalog, false);
1203                 // add the info-object to the body
1204                 PdfIndirectObject infoObj = addToBody(getInfo(), false);
1205
1206                 // [F1] encryption
1207                 PdfIndirectReference encryption = null;
1208                 PdfObject fileID = null;
1209                 body.flushObjStm();
1210                 if (crypto != null) {
1211                     PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false);
1212                     encryption = encryptionObject.getIndirectReference();
1213                     fileID = crypto.getFileID();
1214                 }
1215                 else
1216                     fileID = PdfEncryption.createInfoId(PdfEncryption.createDocumentId());
1217
1218                 // write the cross-reference table of the body
1219                 body.writeCrossReferenceTable(os, indirectCatalog.getIndirectReference(),
1220                     infoObj.getIndirectReference(), encryption,  fileID, prevxref);
1221
1222                 // make the trailer
1223                 // [F2] full compression
1224                 if (fullCompression) {
1225                     os.write(getISOBytes("startxref\n"));
1226                     os.write(getISOBytes(String.valueOf(body.offset())));
1227                     os.write(getISOBytes("\n%%EOF\n"));
1228                 }
1229                 else {
1230                     PdfTrailer trailer = new PdfTrailer(body.size(),
1231                     body.offset(),
1232                     indirectCatalog.getIndirectReference(),
1233                     infoObj.getIndirectReference(),
1234                     encryption,
1235                     fileID, prevxref);
1236                     trailer.toPdf(this, os);
1237                 }
1238                 super.close();
1239             }
1240             catch(IOException ioe) {
1241                 throw new ExceptionConverter(ioe);
1242             }
1243         }
1244     }
1245
1246     protected void addSharedObjectsToBody() throws IOException {
1247         // [F3] add the fonts
1248         for (Iterator it = documentFonts.values().iterator(); it.hasNext();) {
1249             FontDetails details = (FontDetails)it.next();
1250             details.writeFont(this);
1251         }
1252         // [F4] add the form XObjects
1253         for (Iterator it = formXObjects.values().iterator(); it.hasNext();) {
1254             Object objs[] = (Object[])it.next();
1255             PdfTemplate template = (PdfTemplate)objs[1];
1256             if (template != null && template.getIndirectReference() instanceof PRIndirectReference)
1257                 continue;
1258             if (template != null && template.getType() == PdfTemplate.TYPE_TEMPLATE) {
1259                 addToBody(template.getFormXObject(compressionLevel), template.getIndirectReference());
1260             }
1261         }
1262         // [F5] add all the dependencies in the imported pages
1263         for (Iterator it = importedPages.values().iterator(); it.hasNext();) {
1264             currentPdfReaderInstance = (PdfReaderInstance)it.next();
1265             currentPdfReaderInstance.writeAllPages();
1266         }
1267         currentPdfReaderInstance = null;
1268         // [F6] add the spotcolors
1269         for (Iterator it = documentColors.values().iterator(); it.hasNext();) {
1270             ColorDetails color = (ColorDetails)it.next();
1271             addToBody(color.getSpotColor(this), color.getIndirectReference());
1272         }
1273         // [F7] add the pattern
1274         for (Iterator it = documentPatterns.keySet().iterator(); it.hasNext();) {
1275             PdfPatternPainter pat = (PdfPatternPainter)it.next();
1276             addToBody(pat.getPattern(compressionLevel), pat.getIndirectReference());
1277         }
1278         // [F8] add the shading patterns
1279         for (Iterator it = documentShadingPatterns.keySet().iterator(); it.hasNext();) {
1280             PdfShadingPattern shadingPattern = (PdfShadingPattern)it.next();
1281             shadingPattern.addToBody();
1282         }
1283         // [F9] add the shadings
1284         for (Iterator it = documentShadings.keySet().iterator(); it.hasNext();) {
1285             PdfShading shading = (PdfShading)it.next();
1286             shading.addToBody();
1287         }
1288         // [F10] add the extgstate
1289         for (Iterator it = documentExtGState.entrySet().iterator(); it.hasNext();) {
1290             Map.Entry entry = (Map.Entry) it.next();
1291             PdfDictionary gstate = (PdfDictionary) entry.getKey();
1292             PdfObject obj[] = (PdfObject[]) entry.getValue();
1293             addToBody(gstate, (PdfIndirectReference)obj[1]);
1294         }
1295         // [F11] add the properties
1296         for (Iterator it = documentProperties.entrySet().iterator(); it.hasNext();) {
1297             Map.Entry entry = (Map.Entry) it.next();
1298             Object prop = entry.getKey();
1299             PdfObject[] obj = (PdfObject[]) entry.getValue();
1300             if (prop instanceof PdfLayerMembership){
1301                 PdfLayerMembership layer = (PdfLayerMembership)prop;
1302                 addToBody(layer.getPdfObject(), layer.getRef());
1303             }
1304             else if ((prop instanceof PdfDictionary) && !(prop instanceof PdfLayer)){
1305                 addToBody((PdfDictionary)prop, (PdfIndirectReference)obj[1]);
1306             }
1307         }
1308         // [F13] add the OCG layers
1309         for (Iterator it = documentOCG.iterator(); it.hasNext();) {
1310             PdfOCG layer = (PdfOCG)it.next();
1311             addToBody(layer.getPdfObject(), layer.getRef());
1312         }
1313     }
1314
1315 // Root data for the PDF document (used when composing the Catalog)
1316
1317 //  [C1] Outlines (bookmarks)
1318
1319      /**
1320       * Use this method to get the root outline
1321       * and construct bookmarks.
1322       * @return the root outline
1323       */

1324
1325      public PdfOutline getRootOutline() {
1326          return directContent.getRootOutline();
1327      }
1328
1329      protected java.util.List newBookmarks;
1330
1331     /**
1332      * Sets the bookmarks. The list structure is defined in
1333      * {@link SimpleBookmark}.
1334      * @param outlines the bookmarks or <CODE>null</CODE> to remove any
1335      */

1336     public void setOutlines(java.util.List outlines) {
1337         newBookmarks = outlines;
1338     }
1339
1340     protected void writeOutlines(PdfDictionary catalog, boolean namedAsNames) throws IOException {
1341         if (newBookmarks == null || newBookmarks.isEmpty())
1342             return;
1343         PdfDictionary top = new PdfDictionary();
1344         PdfIndirectReference topRef = getPdfIndirectReference();
1345         Object kids[] = SimpleBookmark.iterateOutlines(this, topRef, newBookmarks, namedAsNames);
1346         top.put(PdfName.FIRST, (PdfIndirectReference)kids[0]);
1347         top.put(PdfName.LAST, (PdfIndirectReference)kids[1]);
1348         top.put(PdfName.COUNT, new PdfNumber(((Integer)kids[2]).intValue()));
1349         addToBody(top, topRef);
1350         catalog.put(PdfName.OUTLINES, topRef);
1351     }
1352
1353 //    [C2] PdfVersion interface
1354      /** possible PDF version (header) */
1355      public static final char VERSION_1_2 = '2';
1356      /** possible PDF version (header) */
1357      public static final char VERSION_1_3 = '3';
1358      /** possible PDF version (header) */
1359      public static final char VERSION_1_4 = '4';
1360      /** possible PDF version (header) */
1361      public static final char VERSION_1_5 = '5';
1362      /** possible PDF version (header) */
1363      public static final char VERSION_1_6 = '6';
1364      /** possible PDF version (header) */
1365      public static final char VERSION_1_7 = '7';
1366
1367      /** possible PDF version (catalog) */
1368      public static final PdfName PDF_VERSION_1_2 = new PdfName("1.2");
1369      /** possible PDF version (catalog) */
1370      public static final PdfName PDF_VERSION_1_3 = new PdfName("1.3");
1371      /** possible PDF version (catalog) */
1372      public static final PdfName PDF_VERSION_1_4 = new PdfName("1.4");
1373      /** possible PDF version (catalog) */
1374      public static final PdfName PDF_VERSION_1_5 = new PdfName("1.5");
1375      /** possible PDF version (catalog) */
1376      public static final PdfName PDF_VERSION_1_6 = new PdfName("1.6");
1377      /** possible PDF version (catalog) */
1378      public static final PdfName PDF_VERSION_1_7 = new PdfName("1.7");
1379
1380     /** Stores the version information for the header and the catalog. */
1381     protected PdfVersionImp pdf_version = new PdfVersionImp();
1382
1383     /** @see com.lowagie.text.pdf.interfaces.PdfVersion#setPdfVersion(char) */
1384     public void setPdfVersion(char version) {
1385         pdf_version.setPdfVersion(version);
1386     }
1387
1388     /** @see com.lowagie.text.pdf.interfaces.PdfVersion#setAtLeastPdfVersion(char) */
1389     public void setAtLeastPdfVersion(char version) {
1390         pdf_version.setAtLeastPdfVersion(version);
1391     }
1392
1393     /** @see com.lowagie.text.pdf.interfaces.PdfVersion#setPdfVersion(com.lowagie.text.pdf.PdfName) */
1394     public void setPdfVersion(PdfName version) {
1395         pdf_version.setPdfVersion(version);
1396     }
1397
1398     /**
1399      * @see com.lowagie.text.pdf.interfaces.PdfVersion#addDeveloperExtension(com.lowagie.text.pdf.PdfDeveloperExtension)
1400      * @since    2.1.6
1401      */

1402     public void addDeveloperExtension(PdfDeveloperExtension de) {
1403         pdf_version.addDeveloperExtension(de);
1404     }
1405     
1406     /**
1407      * Returns the version information.
1408      */

1409     PdfVersionImp getPdfVersion() {
1410         return pdf_version;
1411     }
1412
1413 //  [C3] PdfViewerPreferences interface
1414
1415     // page layout (section 13.1.1 of "iText in Action")
1416
1417     /** A viewer preference */
1418     public static final int PageLayoutSinglePage = 1;
1419     /** A viewer preference */
1420     public static final int PageLayoutOneColumn = 2;
1421     /** A viewer preference */
1422     public static final int PageLayoutTwoColumnLeft = 4;
1423     /** A viewer preference */
1424     public static final int PageLayoutTwoColumnRight = 8;
1425     /** A viewer preference */
1426     public static final int PageLayoutTwoPageLeft = 16;
1427     /** A viewer preference */
1428     public static final int PageLayoutTwoPageRight = 32;
1429
1430     // page mode (section 13.1.2 of "iText in Action")
1431
1432     /** A viewer preference */
1433     public static final int PageModeUseNone = 64;
1434     /** A viewer preference */
1435     public static final int PageModeUseOutlines = 128;
1436     /** A viewer preference */
1437     public static final int PageModeUseThumbs = 256;
1438     /** A viewer preference */
1439     public static final int PageModeFullScreen = 512;
1440     /** A viewer preference */
1441     public static final int PageModeUseOC = 1024;
1442     /** A viewer preference */
1443     public static final int PageModeUseAttachments = 2048;
1444
1445     // values for setting viewer preferences in iText versions older than 2.x
1446
1447     /** A viewer preference */
1448     public static final int HideToolbar = 1 << 12;
1449     /** A viewer preference */
1450     public static final int HideMenubar = 1 << 13;
1451     /** A viewer preference */
1452     public static final int HideWindowUI = 1 << 14;
1453     /** A viewer preference */
1454     public static final int FitWindow = 1 << 15;
1455     /** A viewer preference */
1456     public static final int CenterWindow = 1 << 16;
1457     /** A viewer preference */
1458     public static final int DisplayDocTitle = 1 << 17;
1459
1460     /** A viewer preference */
1461     public static final int NonFullScreenPageModeUseNone = 1 << 18;
1462     /** A viewer preference */
1463     public static final int NonFullScreenPageModeUseOutlines = 1 << 19;
1464     /** A viewer preference */
1465     public static final int NonFullScreenPageModeUseThumbs = 1 << 20;
1466     /** A viewer preference */
1467     public static final int NonFullScreenPageModeUseOC = 1 << 21;
1468
1469     /** A viewer preference */
1470     public static final int DirectionL2R = 1 << 22;
1471     /** A viewer preference */
1472     public static final int DirectionR2L = 1 << 23;
1473
1474     /** A viewer preference */
1475     public static final int PrintScalingNone = 1 << 24;
1476
1477     /** @see com.lowagie.text.pdf.interfaces.PdfViewerPreferences#setViewerPreferences(int) */
1478     public void setViewerPreferences(int preferences) {
1479         pdf.setViewerPreferences(preferences);
1480     }
1481
1482     /** @see com.lowagie.text.pdf.interfaces.PdfViewerPreferences#addViewerPreference(com.lowagie.text.pdf.PdfName, com.lowagie.text.pdf.PdfObject) */
1483     public void addViewerPreference(PdfName key, PdfObject value) {
1484         pdf.addViewerPreference(key, value);
1485     }
1486
1487 //  [C4] Page labels
1488
1489     /**
1490      * Use this method to add page labels
1491      * @param pageLabels the page labels
1492      */

1493     public void setPageLabels(PdfPageLabels pageLabels) {
1494         pdf.setPageLabels(pageLabels);
1495     }
1496
1497 //  [C5] named objects: named destinations, javascript, embedded files
1498
1499      /**
1500       * Use this method to add a JavaScript action at the document level.
1501       * When the document opens, all this JavaScript runs.
1502       * @param js The JavaScript action
1503       */

1504      public void addJavaScript(PdfAction js) {
1505          pdf.addJavaScript(js);
1506      }
1507
1508      /**
1509       * Use this method to add a JavaScript action at the document level.
1510       * When the document opens, all this JavaScript runs.
1511       * @param code the JavaScript code
1512       * @param unicode select JavaScript unicode. Note that the internal
1513       * Acrobat JavaScript engine does not support unicode,
1514       * so this may or may not work for you
1515       */

1516      public void addJavaScript(String code, boolean unicode) {
1517          addJavaScript(PdfAction.javaScript(code, this, unicode));
1518      }
1519
1520      /**
1521       * Use this method to adds a JavaScript action at the document level.
1522       * When the document opens, all this JavaScript runs.
1523       * @param code the JavaScript code
1524       */

1525      public void addJavaScript(String code) {
1526          addJavaScript(code, false);
1527      }
1528      /**
1529       * Use this method to add a JavaScript action at the document level.
1530       * When the document opens, all this JavaScript runs.
1531       * @param name    The name of the JS Action in the name tree
1532       * @param js The JavaScript action
1533       */

1534      public void addJavaScript(String name, PdfAction js) {
1535          pdf.addJavaScript(name, js);
1536      }
1537
1538      /**
1539       * Use this method to add a JavaScript action at the document level.
1540       * When the document opens, all this JavaScript runs.
1541       * @param name    The name of the JS Action in the name tree
1542       * @param code the JavaScript code
1543       * @param unicode select JavaScript unicode. Note that the internal
1544       * Acrobat JavaScript engine does not support unicode,
1545       * so this may or may not work for you
1546       */

1547      public void addJavaScript(String name, String code, boolean unicode) {
1548          addJavaScript(name, PdfAction.javaScript(code, this, unicode));
1549      }
1550
1551      /**
1552       * Use this method to adds a JavaScript action at the document level.
1553       * When the document opens, all this JavaScript runs.
1554       * @param name    The name of the JS Action in the name tree
1555       * @param code the JavaScript code
1556       */

1557      public void addJavaScript(String name, String code) {
1558          addJavaScript(name, code, false);
1559      }
1560
1561      /**
1562       * Use this method to add a file attachment at the document level.
1563       * @param description the file description
1564       * @param fileStore an array with the file. If it's <CODE>null</CODE>
1565       * the file will be read from the disk
1566       * @param file the path to the file. It will only be used if
1567       * <CODE>fileStore</CODE> is not <CODE>null</CODE>
1568       * @param fileDisplay the actual file name stored in the pdf
1569       * @throws IOException on error
1570       */

1571      public void addFileAttachment(String description, byte fileStore[], String file, String fileDisplay) throws IOException {
1572          addFileAttachment(description, PdfFileSpecification.fileEmbedded(this, file, fileDisplay, fileStore));
1573      }
1574
1575      /**
1576       * Use this method to add a file attachment at the document level.
1577       * @param description the file description
1578       * @param fs the file specification
1579       */

1580      public void addFileAttachment(String description, PdfFileSpecification fs) throws IOException {
1581          pdf.addFileAttachment(description, fs);
1582      }
1583
1584      /**
1585       * Use this method to add a file attachment at the document level.
1586       * @param fs the file specification
1587       */

1588      public void addFileAttachment(PdfFileSpecification fs) throws IOException {
1589          addFileAttachment(null, fs);
1590      }
1591
1592 // [C6] Actions (open and additional)
1593
1594      /** action value */
1595      public static final PdfName DOCUMENT_CLOSE = PdfName.WC;
1596      /** action value */
1597      public static final PdfName WILL_SAVE = PdfName.WS;
1598      /** action value */
1599      public static final PdfName DID_SAVE = PdfName.DS;
1600      /** action value */
1601      public static final PdfName WILL_PRINT = PdfName.WP;
1602      /** action value */
1603      public static final PdfName DID_PRINT = PdfName.DP;
1604
1605     /** @see com.lowagie.text.pdf.interfaces.PdfDocumentActions#setOpenAction(java.lang.String) */
1606     public void setOpenAction(String name) {
1607          pdf.setOpenAction(name);
1608      }
1609
1610     /** @see com.lowagie.text.pdf.interfaces.PdfDocumentActions#setOpenAction(com.lowagie.text.pdf.PdfAction) */
1611     public void setOpenAction(PdfAction action) {
1612          pdf.setOpenAction(action);
1613      }
1614
1615     /** @see com.lowagie.text.pdf.interfaces.PdfDocumentActions#setAdditionalAction(com.lowagie.text.pdf.PdfName, com.lowagie.text.pdf.PdfAction) */
1616     public void setAdditionalAction(PdfName actionType, PdfAction action) throws DocumentException {
1617          if (!(actionType.equals(DOCUMENT_CLOSE) ||
1618          actionType.equals(WILL_SAVE) ||
1619          actionType.equals(DID_SAVE) ||
1620          actionType.equals(WILL_PRINT) ||
1621          actionType.equals(DID_PRINT))) {
1622              throw new DocumentException("Invalid additional action type: " + actionType.toString());
1623          }
1624          pdf.addAdditionalAction(actionType, action);
1625      }
1626
1627 //  [C7] portable collections
1628
1629     /**
1630      * Use this method to add the Collection dictionary.
1631      * @param collection a dictionary of type PdfCollection
1632      */

1633     public void setCollection(PdfCollection collection) {
1634         setAtLeastPdfVersion(VERSION_1_7);
1635         pdf.setCollection(collection);
1636     }
1637
1638 //  [C8] AcroForm
1639
1640     /** signature value */
1641     public static final int SIGNATURE_EXISTS = 1;
1642     /** signature value */
1643     public static final int SIGNATURE_APPEND_ONLY = 2;
1644
1645     /** @see com.lowagie.text.pdf.interfaces.PdfAnnotations#getAcroForm() */
1646     public PdfAcroForm getAcroForm() {
1647         return pdf.getAcroForm();
1648     }
1649
1650     /** @see com.lowagie.text.pdf.interfaces.PdfAnnotations#addAnnotation(com.lowagie.text.pdf.PdfAnnotation) */
1651     public void addAnnotation(PdfAnnotation annot) {
1652         pdf.addAnnotation(annot);
1653     }
1654
1655     void addAnnotation(PdfAnnotation annot, int page) {
1656         addAnnotation(annot);
1657     }
1658
1659     /** @see com.lowagie.text.pdf.interfaces.PdfAnnotations#addCalculationOrder(com.lowagie.text.pdf.PdfFormField) */
1660     public void addCalculationOrder(PdfFormField annot) {
1661         pdf.addCalculationOrder(annot);
1662     }
1663
1664     /** @see com.lowagie.text.pdf.interfaces.PdfAnnotations#setSigFlags(int) */
1665     public void setSigFlags(int f) {
1666         pdf.setSigFlags(f);
1667     }
1668
1669 //  [C9] Metadata
1670
1671     /** XMP Metadata for the document. */
1672     protected byte[] xmpMetadata = null;
1673
1674     /**
1675      * Use this method to set the XMP Metadata.
1676      * @param xmpMetadata The xmpMetadata to set.
1677      */

1678     public void setXmpMetadata(byte[] xmpMetadata) {
1679         this.xmpMetadata = xmpMetadata;
1680     }
1681
1682     /**
1683      * Use this method to set the XMP Metadata for each page.
1684      * @param xmpMetadata The xmpMetadata to set.
1685      */

1686     public void setPageXmpMetadata(byte[] xmpMetadata) {
1687         pdf.setXmpMetadata(xmpMetadata);
1688     }
1689
1690     /**
1691      * Use this method to creates XMP Metadata based
1692      * on the metadata in the PdfDocument.
1693      */

1694     public void createXmpMetadata() {
1695         setXmpMetadata(createXmpMetadataBytes());
1696     }
1697
1698     /**
1699      * @return an XmpMetadata byte array
1700      */

1701     private byte[] createXmpMetadataBytes() {
1702         ByteArrayOutputStream baos = new ByteArrayOutputStream();
1703         try {
1704             XmpWriter xmp = new XmpWriter(baos, pdf.getInfo(), pdfxConformance.getPDFXConformance());
1705             xmp.close();
1706         }
1707         catch (IOException ioe) {
1708             ioe.printStackTrace();
1709         }
1710         return baos.toByteArray();
1711     }
1712
1713 //  [C10] PDFX Conformance
1714     /** A PDF/X level. */
1715     public static final int PDFXNONE = 0;
1716     /** A PDF/X level. */
1717     public static final int PDFX1A2001 = 1;
1718     /** A PDF/X level. */
1719     public static final int PDFX32002 = 2;
1720     /** PDFA-1A level. */
1721     public static final int PDFA1A = 3;
1722     /** PDFA-1B level. */
1723     public static final int PDFA1B = 4;
1724
1725     /** Stores the PDF/X level. */
1726     private PdfXConformanceImp pdfxConformance = new PdfXConformanceImp();
1727
1728     /** @see com.lowagie.text.pdf.interfaces.PdfXConformance#setPDFXConformance(int) */
1729     public void setPDFXConformance(int pdfx) {
1730         if (pdfxConformance.getPDFXConformance() == pdfx)
1731             return;
1732         if (pdf.isOpen())
1733             throw new PdfXConformanceException("PDFX conformance can only be set before opening the document.");
1734         if (crypto != null)
1735             throw new PdfXConformanceException("A PDFX conforming document cannot be encrypted.");
1736         if (pdfx == PDFA1A || pdfx == PDFA1B)
1737             setPdfVersion(VERSION_1_4);
1738         else if (pdfx != PDFXNONE)
1739             setPdfVersion(VERSION_1_3);
1740         pdfxConformance.setPDFXConformance(pdfx);
1741     }
1742
1743     /** @see com.lowagie.text.pdf.interfaces.PdfXConformance#getPDFXConformance() */
1744     public int getPDFXConformance() {
1745         return pdfxConformance.getPDFXConformance();
1746     }
1747
1748     /** @see com.lowagie.text.pdf.interfaces.PdfXConformance#isPdfX() */
1749     public boolean isPdfX() {
1750         return pdfxConformance.isPdfX();
1751     }
1752
1753 //  [C11] Output intents
1754     /**
1755      * Sets the values of the output intent dictionary. Null values are allowed to
1756      * suppress any key.
1757      *
1758      * @param outputConditionIdentifier a value
1759      * @param outputCondition           a value, "PDFA/A" to force GTS_PDFA1, otherwise cued by pdfxConformance.
1760      * @param registryName              a value
1761      * @param info                      a value
1762      * @param colorProfile              a value
1763      * @since 2.1.5
1764      * @throws IOException on error
1765      */

1766     public void setOutputIntents(String outputConditionIdentifier, String outputCondition, String registryName, String info, ICC_Profile colorProfile) throws IOException {
1767         getExtraCatalog();
1768         PdfDictionary out = new PdfDictionary(PdfName.OUTPUTINTENT);
1769         if (outputCondition != null)
1770             out.put(PdfName.OUTPUTCONDITION, new PdfString(outputCondition, PdfObject.TEXT_UNICODE));
1771         if (outputConditionIdentifier != null)
1772             out.put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString(outputConditionIdentifier, PdfObject.TEXT_UNICODE));
1773         if (registryName != null)
1774             out.put(PdfName.REGISTRYNAME, new PdfString(registryName, PdfObject.TEXT_UNICODE));
1775         if (info != null)
1776             out.put(PdfName.INFO, new PdfString(info, PdfObject.TEXT_UNICODE));
1777         if (colorProfile != null) {
1778             PdfStream stream = new PdfICCBased(colorProfile, compressionLevel);
1779             out.put(PdfName.DESTOUTPUTPROFILE, addToBody(stream).getIndirectReference());
1780         }
1781
1782         PdfName intentSubtype;
1783         if (pdfxConformance.isPdfA1() || "PDFA/1".equals(outputCondition)) {
1784             intentSubtype = PdfName.GTS_PDFA1;
1785         }
1786         else {
1787             intentSubtype = PdfName.GTS_PDFX;
1788         }
1789
1790         out.put(PdfName.S, intentSubtype);
1791
1792         extraCatalog.put(PdfName.OUTPUTINTENTS, new PdfArray(out));
1793     }
1794
1795    /**
1796      * Sets the values of the output intent dictionary. Null values are allowed to
1797      * suppress any key.
1798      *
1799      * Prefer the <CODE>ICC_Profile</CODE>-based version of this method.
1800      * @param outputConditionIdentifier a value
1801      * @param outputCondition           a value, "PDFA/A" to force GTS_PDFA1, otherwise cued by pdfxConformance.
1802      * @param registryName              a value
1803      * @param info                      a value
1804      * @param destOutputProfile         a value
1805      * @since 1.x
1806      *
1807      * @throws IOException
1808      */

1809     public void setOutputIntents(String outputConditionIdentifier, String outputCondition, String registryName, String info, byte destOutputProfile[]) throws IOException {
1810         ICC_Profile colorProfile = (destOutputProfile == null) ? null : ICC_Profile.getInstance(destOutputProfile);
1811         setOutputIntents(outputConditionIdentifier, outputCondition, registryName, info, colorProfile);
1812     }
1813
1814
1815     /**
1816      * Use this method to copy the output intent dictionary
1817      * from another document to this one.
1818      * @param reader the other document
1819      * @param checkExistence <CODE>true</CODE> to just check for the existence of a valid output intent
1820      * dictionary, <CODE>false</CODE> to insert the dictionary if it exists
1821      * @throws IOException on error
1822      * @return <CODE>true</CODE> if the output intent dictionary exists, <CODE>false</CODE>
1823      * otherwise
1824      */

1825     public boolean setOutputIntents(PdfReader reader, boolean checkExistence) throws IOException {
1826         PdfDictionary catalog = reader.getCatalog();
1827         PdfArray outs = catalog.getAsArray(PdfName.OUTPUTINTENTS);
1828         if (outs == null)
1829             return false;
1830         if (outs.isEmpty())
1831             return false;
1832         PdfDictionary out = outs.getAsDict(0);
1833         PdfObject obj = PdfReader.getPdfObject(out.get(PdfName.S));
1834         if (obj == null || !PdfName.GTS_PDFX.equals(obj))
1835             return false;
1836         if (checkExistence)
1837             return true;
1838         PRStream stream = (PRStream)PdfReader.getPdfObject(out.get(PdfName.DESTOUTPUTPROFILE));
1839         byte destProfile[] = null;
1840         if (stream != null) {
1841             destProfile = PdfReader.getStreamBytes(stream);
1842         }
1843         setOutputIntents(getNameString(out, PdfName.OUTPUTCONDITIONIDENTIFIER), getNameString(out, PdfName.OUTPUTCONDITION),
1844             getNameString(out, PdfName.REGISTRYNAME), getNameString(out, PdfName.INFO), destProfile);
1845         return true;
1846     }
1847
1848     private static String getNameString(PdfDictionary dic, PdfName key) {
1849         PdfObject obj = PdfReader.getPdfObject(dic.get(key));
1850         if (obj == null || !obj.isString())
1851             return null;
1852         return ((PdfString)obj).toUnicodeString();
1853     }
1854
1855 // PDF Objects that have an impact on the PDF body
1856
1857 //  [F1] PdfEncryptionSettings interface
1858
1859     // types of encryption
1860
1861     /** Type of encryption */
1862     public static final int STANDARD_ENCRYPTION_40 = 0;
1863     /** Type of encryption */
1864     public static final int STANDARD_ENCRYPTION_128 = 1;
1865     /** Type of encryption */
1866     public static final int ENCRYPTION_AES_128 = 2;
1867     /** Mask to separate the encryption type from the encryption mode. */
1868     static final int ENCRYPTION_MASK = 7;
1869     /** Add this to the mode to keep the metadata in clear text */
1870     public static final int DO_NOT_ENCRYPT_METADATA = 8;
1871     /**
1872      * Add this to the mode to keep encrypt only the embedded files.
1873      * @since 2.1.3
1874      */

1875     public static final int EMBEDDED_FILES_ONLY = 24;
1876
1877     // permissions
1878
1879     /** The operation permitted when the document is opened with the user password
1880      *
1881      * @since 2.0.7
1882      */

1883     public static final int ALLOW_PRINTING = 4 + 2048;
1884
1885     /** The operation permitted when the document is opened with the user password
1886      *
1887      * @since 2.0.7
1888      */

1889     public static final int ALLOW_MODIFY_CONTENTS = 8;
1890
1891     /** The operation permitted when the document is opened with the user password
1892      *
1893      * @since 2.0.7
1894      */

1895     public static final int ALLOW_COPY = 16;
1896
1897     /** The operation permitted when the document is opened with the user password
1898      *
1899      * @since 2.0.7
1900      */

1901     public static final int ALLOW_MODIFY_ANNOTATIONS = 32;
1902
1903     /** The operation permitted when the document is opened with the user password
1904      *
1905      * @since 2.0.7
1906      */

1907     public static final int ALLOW_FILL_IN = 256;
1908
1909     /** The operation permitted when the document is opened with the user password
1910      *
1911      * @since 2.0.7
1912      */

1913     public static final int ALLOW_SCREENREADERS = 512;
1914
1915     /** The operation permitted when the document is opened with the user password
1916      *
1917      * @since 2.0.7
1918      */

1919     public static final int ALLOW_ASSEMBLY = 1024;
1920
1921     /** The operation permitted when the document is opened with the user password
1922      *
1923      * @since 2.0.7
1924      */

1925     public static final int ALLOW_DEGRADED_PRINTING = 4;
1926
1927     /** @deprecated As of iText 2.0.7, use {@link #ALLOW_PRINTING} instead. Scheduled for removal at or after 2.2.0 */
1928     public static final int AllowPrinting = ALLOW_PRINTING;
1929     /** @deprecated As of iText 2.0.7, use {@link #ALLOW_MODIFY_CONTENTS} instead. Scheduled for removal at or after 2.2.0 */
1930     public static final int AllowModifyContents = ALLOW_MODIFY_CONTENTS;
1931     /** @deprecated As of iText 2.0.7, use {@link #ALLOW_COPY} instead. Scheduled for removal at or after 2.2.0 */
1932     public static final int AllowCopy = ALLOW_COPY;
1933     /** @deprecated As of iText 2.0.7, use {@link #ALLOW_MODIFY_ANNOTATIONS} instead. Scheduled for removal at or after 2.2.0 */
1934     public static final int AllowModifyAnnotations = ALLOW_MODIFY_ANNOTATIONS;
1935     /** @deprecated As of iText 2.0.7, use {@link #ALLOW_FILL_IN} instead. Scheduled for removal at or after 2.2.0 */
1936     public static final int AllowFillIn = ALLOW_FILL_IN;
1937     /** @deprecated As of iText 2.0.7, use {@link #ALLOW_SCREENREADERS} instead. Scheduled for removal at or after 2.2.0 */
1938     public static final int AllowScreenReaders = ALLOW_SCREENREADERS;
1939     /** @deprecated As of iText 2.0.7, use {@link #ALLOW_ASSEMBLY} instead. Scheduled for removal at or after 2.2.0 */
1940     public static final int AllowAssembly = ALLOW_ASSEMBLY;
1941     /** @deprecated As of iText 2.0.7, use {@link #ALLOW_DEGRADED_PRINTING} instead. Scheduled for removal at or after 2.2.0 */
1942     public static final int AllowDegradedPrinting = ALLOW_DEGRADED_PRINTING;
1943
1944     // Strength of the encryption (kept for historical reasons)
1945     /** @deprecated As of iText 2.0.7, use {@link #STANDARD_ENCRYPTION_40} instead. Scheduled for removal at or after 2.2.0 */
1946     public static final boolean STRENGTH40BITS = false;
1947     /** @deprecated As of iText 2.0.7, use {@link #STANDARD_ENCRYPTION_128} instead. Scheduled for removal at or after 2.2.0 */
1948     public static final boolean STRENGTH128BITS = true;
1949
1950     /** Contains the business logic for cryptography. */
1951     protected PdfEncryption crypto;
1952     PdfEncryption getEncryption() {
1953         return crypto;
1954     }
1955
1956     /** @see com.lowagie.text.pdf.interfaces.PdfEncryptionSettings#setEncryption(byte[], byte[], intint) */
1957     public void setEncryption(byte userPassword[], byte ownerPassword[], int permissions, int encryptionType) throws DocumentException {
1958         if (pdf.isOpen())
1959             throw new DocumentException("Encryption can only be added before opening the document.");
1960         crypto = new PdfEncryption();
1961         crypto.setCryptoMode(encryptionType, 0);
1962         crypto.setupAllKeys(userPassword, ownerPassword, permissions);
1963     }
1964
1965     /** @see com.lowagie.text.pdf.interfaces.PdfEncryptionSettings#setEncryption(java.security.cert.Certificate[], int[], int) */
1966     public void setEncryption(Certificate[] certs, int[] permissions, int encryptionType) throws DocumentException {
1967         if (pdf.isOpen())
1968             throw new DocumentException("Encryption can only be added before opening the document.");
1969         crypto = new PdfEncryption();
1970         if (certs != null) {
1971             for (int i=0; i < certs.length; i++) {
1972                 crypto.addRecipient(certs[i], permissions[i]);
1973             }
1974         }
1975         crypto.setCryptoMode(encryptionType, 0);
1976         crypto.getEncryptionDictionary();
1977     }
1978
1979     /**
1980      * Sets the encryption options for this document. The userPassword and the
1981      *  ownerPassword can be null or have zero length. In this case the ownerPassword
1982      *  is replaced by a random string. The open permissions for the document can be
1983      *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
1984      *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
1985      *  The permissions can be combined by ORing them.
1986      * @param userPassword the user password. Can be null or empty
1987      * @param ownerPassword the owner password. Can be null or empty
1988      * @param permissions the user permissions
1989      * @param strength128Bits <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length
1990      * @throws DocumentException if the document is already open
1991      * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], intint)}. Scheduled for removal at or after 2.2.0
1992      */

1993     public void setEncryption(byte userPassword[], byte ownerPassword[], int permissions, boolean strength128Bits) throws DocumentException {
1994         setEncryption(userPassword, ownerPassword, permissions, strength128Bits ? STANDARD_ENCRYPTION_128 : STANDARD_ENCRYPTION_40);
1995     }
1996
1997     /**
1998      * Sets the encryption options for this document. The userPassword and the
1999      *  ownerPassword can be null or have zero length. In this case the ownerPassword
2000      *  is replaced by a random string. The open permissions for the document can be
2001      *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
2002      *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
2003      *  The permissions can be combined by ORing them.
2004      * @param strength <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length
2005      * @param userPassword the user password. Can be null or empty
2006      * @param ownerPassword the owner password. Can be null or empty
2007      * @param permissions the user permissions
2008      * @throws DocumentException if the document is already open
2009      * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], intint)}. Scheduled for removal at or after 2.2.0
2010      */

2011     public void setEncryption(boolean strength, String userPassword, String ownerPassword, int permissions) throws DocumentException {
2012         setEncryption(getISOBytes(userPassword), getISOBytes(ownerPassword), permissions, strength ? STANDARD_ENCRYPTION_128 : STANDARD_ENCRYPTION_40);
2013     }
2014
2015     /**
2016      * Sets the encryption options for this document. The userPassword and the
2017      *  ownerPassword can be null or have zero length. In this case the ownerPassword
2018      *  is replaced by a random string. The open permissions for the document can be
2019      *  AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
2020      *  AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
2021      *  The permissions can be combined by ORing them.
2022      * @param encryptionType the type of encryption. It can be one of STANDARD_ENCRYPTION_40, STANDARD_ENCRYPTION_128 or ENCRYPTION_AES128.
2023      * Optionally DO_NOT_ENCRYPT_METADATA can be ored to output the metadata in cleartext
2024      * @param userPassword the user password. Can be null or empty
2025      * @param ownerPassword the owner password. Can be null or empty
2026      * @param permissions the user permissions
2027      * @throws DocumentException if the document is already open
2028      * @deprecated As of iText 2.0.3, replaced by (@link #setEncryption(byte[], byte[], intint)}. Scheduled for removal at or after 2.2.0
2029      */

2030     public void setEncryption(int encryptionType, String userPassword, String ownerPassword, int permissions) throws DocumentException {
2031         setEncryption(getISOBytes(userPassword), getISOBytes(ownerPassword), permissions, encryptionType);
2032     }
2033
2034 //  [F2] compression
2035
2036     /** Holds value of property fullCompression. */
2037     protected boolean fullCompression = false;
2038
2039     /**
2040      * Use this method to find out if 1.5 compression is on.
2041      * @return the 1.5 compression status
2042      */

2043     public boolean isFullCompression() {
2044         return this.fullCompression;
2045     }
2046
2047     /**
2048      * Use this method to set the document's compression to the
2049      * PDF 1.5 mode with object streams and xref streams.
2050      * It can be set at any time but once set it can't be unset.
2051      * <p>
2052      * If set before opening the document it will also set the pdf version to 1.5.
2053      */

2054     public void setFullCompression() {
2055         this.fullCompression = true;
2056         setAtLeastPdfVersion(VERSION_1_5);
2057     }
2058
2059     /**
2060      * The compression level of the content streams.
2061      * @since 2.1.3
2062      */

2063     protected int compressionLevel = PdfStream.DEFAULT_COMPRESSION;
2064
2065     /**
2066      * Returns the compression level used for streams written by this writer.
2067      * @return the compression level (0 = best speed, 9 = best compression, -1 is default)
2068      * @since 2.1.3
2069      */

2070     public int getCompressionLevel() {
2071         return compressionLevel;
2072     }
2073
2074     /**
2075      * Sets the compression level to be used for streams written by this writer.
2076      * @param compressionLevel a value between 0 (best speed) and 9 (best compression)
2077      * @since 2.1.3
2078      */

2079     public void setCompressionLevel(int compressionLevel) {
2080         if (compressionLevel < PdfStream.NO_COMPRESSION || compressionLevel > PdfStream.BEST_COMPRESSION)
2081             this.compressionLevel = PdfStream.DEFAULT_COMPRESSION;
2082         else
2083             this.compressionLevel = compressionLevel;
2084     }
2085
2086 //  [F3] adding fonts
2087
2088     /** The fonts of this document */
2089     protected LinkedHashMap documentFonts = new LinkedHashMap();
2090
2091     /** The font number counter for the fonts in the document. */
2092     protected int fontNumber = 1;
2093
2094     /**
2095      * Adds a <CODE>BaseFont</CODE> to the document but not to the page resources.
2096      * It is used for templates.
2097      * @param bf the <CODE>BaseFont</CODE> to add
2098      * @return an <CODE>Object[]</CODE> where position 0 is a <CODE>PdfName</CODE>
2099      * and position 1 is an <CODE>PdfIndirectReference</CODE>
2100      */

2101
2102     FontDetails addSimple(BaseFont bf) {
2103         if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) {
2104             return new FontDetails(new PdfName("F" + (fontNumber++)), ((DocumentFont)bf).getIndirectReference(), bf);
2105         }
2106         FontDetails ret = (FontDetails)documentFonts.get(bf);
2107         if (ret == null) {
2108             PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_FONT, bf);
2109             ret = new FontDetails(new PdfName("F" + (fontNumber++)), body.getPdfIndirectReference(), bf);
2110             documentFonts.put(bf, ret);
2111         }
2112         return ret;
2113     }
2114
2115     void eliminateFontSubset(PdfDictionary fonts) {
2116         for (Iterator it = documentFonts.values().iterator(); it.hasNext();) {
2117             FontDetails ft = (FontDetails)it.next();
2118             if (fonts.get(ft.getFontName()) != null)
2119                 ft.setSubset(false);
2120         }
2121     }
2122
2123 //  [F4] adding (and releasing) form XObjects
2124
2125     /** The form XObjects in this document. The key is the xref and the value
2126         is Object[]{PdfName, template}.*/

2127     protected HashMap formXObjects = new HashMap();
2128
2129     /** The name counter for the form XObjects name. */
2130     protected int formXObjectsCounter = 1;
2131
2132     /**
2133      * Adds a template to the document but not to the page resources.
2134      * @param template the template to add
2135      * @param forcedName the template name, rather than a generated one. Can be null
2136      * @return the <CODE>PdfName</CODE> for this template
2137      */

2138
2139     PdfName addDirectTemplateSimple(PdfTemplate template, PdfName forcedName) {
2140         PdfIndirectReference ref = template.getIndirectReference();
2141         Object obj[] = (Object[])formXObjects.get(ref);
2142         PdfName name = null;
2143         try {
2144             if (obj == null) {
2145                 if (forcedName == null) {
2146                     name = new PdfName("Xf" + formXObjectsCounter);
2147                     ++formXObjectsCounter;
2148                 }
2149                 else
2150                     name = forcedName;
2151                 if (template.getType() == PdfTemplate.TYPE_IMPORTED) {
2152                     // If we got here from PdfCopy we'll have to fill importedPages
2153                     PdfImportedPage ip = (PdfImportedPage)template;
2154                     PdfReader r = ip.getPdfReaderInstance().getReader();
2155                     if (!importedPages.containsKey(r)) {
2156                         importedPages.put(r, ip.getPdfReaderInstance());
2157                     }
2158                     template = null;
2159                 }
2160                 formXObjects.put(ref, new Object[]{name, template});
2161             }
2162             else
2163                 name = (PdfName)obj[0];
2164         }
2165         catch (Exception e) {
2166             throw new ExceptionConverter(e);
2167         }
2168         return name;
2169     }
2170
2171     /**
2172      * Use this method to releases the memory used by a template.
2173      * This method writes the template to the output.
2174      * The template can still be added to any content
2175      * but changes to the template itself won't have any effect.
2176      * @param tp the template to release
2177      * @throws IOException on error
2178      */

2179     public void releaseTemplate(PdfTemplate tp) throws IOException {
2180         PdfIndirectReference ref = tp.getIndirectReference();
2181         Object[] objs = (Object[])formXObjects.get(ref);
2182         if (objs == null || objs[1] == null)
2183             return;
2184         PdfTemplate template = (PdfTemplate)objs[1];
2185         if (template.getIndirectReference() instanceof PRIndirectReference)
2186             return;
2187         if (template.getType() == PdfTemplate.TYPE_TEMPLATE) {
2188             addToBody(template.getFormXObject(compressionLevel), template.getIndirectReference());
2189             objs[1] = null;
2190         }
2191     }
2192
2193 //  [F5] adding pages imported form other PDF documents
2194
2195     protected HashMap importedPages = new HashMap();
2196
2197     /**
2198      * Use this method to get a page from other PDF document.
2199      * The page can be used as any other PdfTemplate.
2200      * Note that calling this method more than once with the same parameters
2201      * will retrieve the same object.
2202      * @param reader the PDF document where the page is
2203      * @param pageNumber the page number. The first page is 1
2204      * @return the template representing the imported page
2205      */

2206     public PdfImportedPage getImportedPage(PdfReader reader, int pageNumber) {
2207         PdfReaderInstance inst = (PdfReaderInstance)importedPages.get(reader);
2208         if (inst == null) {
2209             inst = reader.getPdfReaderInstance(this);
2210             importedPages.put(reader, inst);
2211         }
2212         return inst.getImportedPage(pageNumber);
2213     }
2214
2215     /**
2216      * Use this method to writes the reader to the document
2217      * and free the memory used by it.
2218      * The main use is when concatenating multiple documents
2219      * to keep the memory usage restricted to the current
2220      * appending document.
2221      * @param reader the <CODE>PdfReader</CODE> to free
2222      * @throws IOException on error
2223      */

2224     public void freeReader(PdfReader reader) throws IOException {
2225         currentPdfReaderInstance = (PdfReaderInstance)importedPages.get(reader);
2226         if (currentPdfReaderInstance == null)
2227             return;
2228         currentPdfReaderInstance.writeAllPages();
2229         currentPdfReaderInstance = null;
2230         importedPages.remove(reader);
2231     }
2232
2233     /**
2234      * Use this method to gets the current document size.
2235      * This size only includes the data already written
2236      * to the output stream, it does not include templates or fonts.
2237      * It is useful if used with <CODE>freeReader()</CODE>
2238      * when concatenating many documents and an idea of
2239      * the current size is needed.
2240      * @return the approximate size without fonts or templates
2241      */

2242     public int getCurrentDocumentSize() {
2243         return body.offset() + body.size() * 20 + 0x48;
2244     }
2245
2246     protected PdfReaderInstance currentPdfReaderInstance;
2247
2248     protected int getNewObjectNumber(PdfReader reader, int number, int generation) {
2249         return currentPdfReaderInstance.getNewObjectNumber(number, generation);
2250     }
2251
2252     RandomAccessFileOrArray getReaderFile(PdfReader reader) {
2253         return currentPdfReaderInstance.getReaderFile();
2254     }
2255
2256 //  [F6] spot colors
2257
2258     /** The colors of this document */
2259     protected HashMap documentColors = new HashMap();
2260
2261     /** The color number counter for the colors in the document. */
2262     protected int colorNumber = 1;
2263
2264     PdfName getColorspaceName() {
2265         return new PdfName("CS" + (colorNumber++));
2266     }
2267
2268     /**
2269      * Adds a <CODE>SpotColor</CODE> to the document but not to the page resources.
2270      * @param spc the <CODE>SpotColor</CODE> to add
2271      * @return an <CODE>Object[]</CODE> where position 0 is a <CODE>PdfName</CODE>
2272      * and position 1 is an <CODE>PdfIndirectReference</CODE>
2273      */

2274     ColorDetails addSimple(PdfSpotColor spc) {
2275         ColorDetails ret = (ColorDetails)documentColors.get(spc);
2276         if (ret == null) {
2277             ret = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), spc);
2278             documentColors.put(spc, ret);
2279         }
2280         return ret;
2281     }
2282
2283 //  [F7] document patterns
2284
2285     /** The patterns of this document */
2286     protected HashMap documentPatterns = new HashMap();
2287
2288     /** The pattern number counter for the colors in the document. */
2289     protected int patternNumber = 1;
2290
2291     PdfName addSimplePattern(PdfPatternPainter painter) {
2292         PdfName name = (PdfName)documentPatterns.get(painter);
2293         try {
2294             if ( name == null ) {
2295                 name = new PdfName("P" + patternNumber);
2296                 ++patternNumber;
2297                 documentPatterns.put(painter, name);
2298             }
2299         } catch (Exception e) {
2300             throw new ExceptionConverter(e);
2301         }
2302         return name;
2303     }
2304
2305 //  [F8] shading patterns
2306
2307     protected HashMap documentShadingPatterns = new HashMap();
2308
2309     void addSimpleShadingPattern(PdfShadingPattern shading) {
2310         if (!documentShadingPatterns.containsKey(shading)) {
2311             shading.setName(patternNumber);
2312             ++patternNumber;
2313             documentShadingPatterns.put(shading, null);
2314             addSimpleShading(shading.getShading());
2315         }
2316     }
2317
2318 //  [F9] document shadings
2319
2320     protected HashMap documentShadings = new HashMap();
2321
2322     void addSimpleShading(PdfShading shading) {
2323         if (!documentShadings.containsKey(shading)) {
2324             documentShadings.put(shading, null);
2325             shading.setName(documentShadings.size());
2326         }
2327     }
2328
2329 // [F10] extended graphics state (for instance for transparency)
2330
2331     protected HashMap documentExtGState = new HashMap();
2332
2333     PdfObject[] addSimpleExtGState(PdfDictionary gstate) {
2334         if (!documentExtGState.containsKey(gstate)) {
2335             PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_GSTATE, gstate);
2336             documentExtGState.put(gstate, new PdfObject[]{new PdfName("GS" + (documentExtGState.size() + 1)), getPdfIndirectReference()});
2337         }
2338         return (PdfObject[])documentExtGState.get(gstate);
2339     }
2340
2341 //  [F11] adding properties (OCG, marked content)
2342
2343     protected HashMap documentProperties = new HashMap();
2344     PdfObject[] addSimpleProperty(Object prop, PdfIndirectReference refi) {
2345         if (!documentProperties.containsKey(prop)) {
2346             if (prop instanceof PdfOCG)
2347                 PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_LAYER, null);
2348             documentProperties.put(prop, new PdfObject[]{new PdfName("Pr" + (documentProperties.size() + 1)), refi});
2349         }
2350         return (PdfObject[])documentProperties.get(prop);
2351     }
2352
2353     boolean propertyExists(Object prop) {
2354         return documentProperties.containsKey(prop);
2355     }
2356
2357 //  [F12] tagged PDF
2358
2359     protected boolean tagged = false;
2360     protected PdfStructureTreeRoot structureTreeRoot;
2361
2362     /**
2363      * Mark this document for tagging. It must be called before open.
2364      */

2365     public void setTagged() {
2366         if (open)
2367             throw new IllegalArgumentException("Tagging must be set before opening the document.");
2368         tagged = true;
2369     }
2370
2371     /**
2372      * Check if the document is marked for tagging.
2373      * @return <CODE>true</CODE> if the document is marked for tagging
2374      */

2375     public boolean isTagged() {
2376         return tagged;
2377     }
2378
2379     /**
2380      * Gets the structure tree root. If the document is not marked for tagging it will return <CODE>null</CODE>.
2381      * @return the structure tree root
2382      */

2383     public PdfStructureTreeRoot getStructureTreeRoot() {
2384         if (tagged && structureTreeRoot == null)
2385             structureTreeRoot = new PdfStructureTreeRoot(this);
2386         return structureTreeRoot;
2387     }
2388
2389 //  [F13] Optional Content Groups
2390     /** A hashSet containing all the PdfLayer objects. */
2391     protected HashSet documentOCG = new HashSet();
2392     /** An array list used to define the order of an OCG tree. */
2393     protected ArrayList documentOCGorder = new ArrayList();
2394     /** The OCProperties in a catalog dictionary. */
2395     protected PdfOCProperties OCProperties;
2396     /** The RBGroups array in an OCG dictionary */
2397     protected PdfArray OCGRadioGroup = new PdfArray();
2398     /**
2399      * The locked array in an OCG dictionary
2400      * @since   2.1.2
2401      */

2402     protected PdfArray OCGLocked = new PdfArray();
2403
2404     /**
2405      * Use this method to get the <B>Optional Content Properties Dictionary</B>.
2406      * Each call fills the dictionary with the current layer state.
2407      * It's advisable to only call this method right before close
2408      * and do any modifications at that time.
2409      * @return the Optional Content Properties Dictionary
2410      */

2411     public PdfOCProperties getOCProperties() {
2412         fillOCProperties(true);
2413         return OCProperties;
2414     }
2415
2416     /**
2417      * Use this method to set a collection of optional content groups
2418      * whose states are intended to follow a "radio button" paradigm.
2419      * That is, the state of at most one optional content group
2420      * in the array should be ON at a time: if one group is turned
2421      * ON, all others must be turned OFF.
2422      * @param group the radio group
2423      */

2424     public void addOCGRadioGroup(ArrayList group) {
2425         PdfArray ar = new PdfArray();
2426         for (int k = 0; k < group.size(); ++k) {
2427             PdfLayer layer = (PdfLayer)group.get(k);
2428             if (layer.getTitle() == null)
2429                 ar.add(layer.getRef());
2430         }
2431         if (ar.size() == 0)
2432             return;
2433         OCGRadioGroup.add(ar);
2434     }
2435
2436     /**
2437      * Use this method to lock an optional content group.
2438      * The state of a locked group cannot be changed through the user interface
2439      * of a viewer application. Producers can use this entry to prevent the visibility
2440      * of content that depends on these groups from being changed by users.
2441      * @param layer    the layer that needs to be added to the array of locked OCGs
2442      * @since    2.1.2
2443      */

2444     public void lockLayer(PdfLayer layer) {
2445         OCGLocked.add(layer.getRef());
2446     }
2447
2448     private static void getOCGOrder(PdfArray order, PdfLayer layer) {
2449         if (!layer.isOnPanel())
2450             return;
2451         if (layer.getTitle() == null)
2452             order.add(layer.getRef());
2453         ArrayList children = layer.getChildren();
2454         if (children == null)
2455             return;
2456         PdfArray kids = new PdfArray();
2457         if (layer.getTitle() != null)
2458             kids.add(new PdfString(layer.getTitle(), PdfObject.TEXT_UNICODE));
2459         for (int k = 0; k < children.size(); ++k) {
2460             getOCGOrder(kids, (PdfLayer)children.get(k));
2461         }
2462         if (kids.size() > 0)
2463             order.add(kids);
2464     }
2465
2466     private void addASEvent(PdfName event, PdfName category) {
2467         PdfArray arr = new PdfArray();
2468         for (Iterator it = documentOCG.iterator(); it.hasNext();) {
2469             PdfLayer layer = (PdfLayer)it.next();
2470             PdfDictionary usage = (PdfDictionary)layer.get(PdfName.USAGE);
2471             if (usage != null && usage.get(category) != null)
2472                 arr.add(layer.getRef());
2473         }
2474         if (arr.size() == 0)
2475             return;
2476         PdfDictionary d = (PdfDictionary)OCProperties.get(PdfName.D);
2477         PdfArray arras = (PdfArray)d.get(PdfName.AS);
2478         if (arras == null) {
2479             arras = new PdfArray();
2480             d.put(PdfName.AS, arras);
2481         }
2482         PdfDictionary as = new PdfDictionary();
2483         as.put(PdfName.EVENT, event);
2484         as.put(PdfName.CATEGORY, new PdfArray(category));
2485         as.put(PdfName.OCGS, arr);
2486         arras.add(as);
2487     }
2488
2489     /**
2490      * @since 2.1.2
2491      */

2492     protected void fillOCProperties(boolean erase) {
2493         if (OCProperties == null)
2494             OCProperties = new PdfOCProperties();
2495         if (erase) {
2496             OCProperties.remove(PdfName.OCGS);
2497             OCProperties.remove(PdfName.D);
2498         }
2499         if (OCProperties.get(PdfName.OCGS) == null) {
2500             PdfArray gr = new PdfArray();
2501             for (Iterator it = documentOCG.iterator(); it.hasNext();) {
2502                 PdfLayer layer = (PdfLayer)it.next();
2503                 gr.add(layer.getRef());
2504             }
2505             OCProperties.put(PdfName.OCGS, gr);
2506         }
2507         if (OCProperties.get(PdfName.D) != null)
2508             return;
2509         ArrayList docOrder = new ArrayList(documentOCGorder);
2510         for (Iterator it = docOrder.iterator(); it.hasNext();) {
2511             PdfLayer layer = (PdfLayer)it.next();
2512             if (layer.getParent() != null)
2513                 it.remove();
2514         }
2515         PdfArray order = new PdfArray();
2516         for (Iterator it = docOrder.iterator(); it.hasNext();) {
2517             PdfLayer layer = (PdfLayer)it.next();
2518             getOCGOrder(order, layer);
2519         }
2520         PdfDictionary d = new PdfDictionary();
2521         OCProperties.put(PdfName.D, d);
2522         d.put(PdfName.ORDER, order);
2523         PdfArray gr = new PdfArray();
2524         for (Iterator it = documentOCG.iterator(); it.hasNext();) {
2525             PdfLayer layer = (PdfLayer)it.next();
2526             if (!layer.isOn())
2527                 gr.add(layer.getRef());
2528         }
2529         if (gr.size() > 0)
2530             d.put(PdfName.OFF, gr);
2531         if (OCGRadioGroup.size() > 0)
2532             d.put(PdfName.RBGROUPS, OCGRadioGroup);
2533         if (OCGLocked.size() > 0)
2534             d.put(PdfName.LOCKED, OCGLocked);
2535         addASEvent(PdfName.VIEW, PdfName.ZOOM);
2536         addASEvent(PdfName.VIEW, PdfName.VIEW);
2537         addASEvent(PdfName.PRINT, PdfName.PRINT);
2538         addASEvent(PdfName.EXPORT, PdfName.EXPORT);
2539         d.put(PdfName.LISTMODE, PdfName.VISIBLEPAGES);
2540     }
2541
2542     void registerLayer(PdfOCG layer) {
2543         PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_LAYER, null);
2544         if (layer instanceof PdfLayer) {
2545             PdfLayer la = (PdfLayer)layer;
2546             if (la.getTitle() == null) {
2547                 if (!documentOCG.contains(layer)) {
2548                     documentOCG.add(layer);
2549                     documentOCGorder.add(layer);
2550                 }
2551             }
2552             else {
2553                 documentOCGorder.add(layer);
2554             }
2555         }
2556         else
2557             throw new IllegalArgumentException("Only PdfLayer is accepted.");
2558     }
2559
2560 //  User methods to change aspects of the page
2561
2562 //  [U1] page size
2563
2564     /**
2565      * Use this method to get the size of the media box.
2566      * @return a Rectangle
2567      */

2568     public Rectangle getPageSize() {
2569         return pdf.getPageSize();
2570     }
2571
2572     /**
2573      * Use this method to set the crop box.
2574      * The crop box should not be rotated even if the page is rotated.
2575      * This change only takes effect in the next page.
2576      * @param crop the crop box
2577      */

2578     public void setCropBoxSize(Rectangle crop) {
2579         pdf.setCropBoxSize(crop);
2580     }
2581
2582     /**
2583      * Use this method to set the page box sizes.
2584      * Allowed names are: "crop""trim""art" and "bleed".
2585      * @param boxName the box size
2586      * @param size the size
2587      */

2588     public void setBoxSize(String boxName, Rectangle size) {
2589         pdf.setBoxSize(boxName, size);
2590     }
2591
2592     /**
2593      * Use this method to get the size of a trim, art, crop or bleed box,
2594      * or null if not defined.
2595      * @param boxName crop, trim, art or bleed
2596      */

2597     public Rectangle getBoxSize(String boxName) {
2598         return pdf.getBoxSize(boxName);
2599     }
2600
2601 //  [U2] take care of empty pages
2602
2603     /**
2604      * Use this method to make sure a page is added,
2605      * even if it's empty. If you use setPageEmpty(false),
2606      * invoking newPage() after a blank page will add a newPage.
2607      * @param pageEmpty the state
2608      */

2609     public void setPageEmpty(boolean pageEmpty) {
2610         pdf.setPageEmpty(pageEmpty);
2611     }
2612
2613 //  [U3] page actions (open and close)
2614
2615     /** action value */
2616     public static final PdfName PAGE_OPEN = PdfName.O;
2617     /** action value */
2618     public static final PdfName PAGE_CLOSE = PdfName.C;
2619
2620     /** @see com.lowagie.text.pdf.interfaces.PdfPageActions#setPageAction(com.lowagie.text.pdf.PdfName, com.lowagie.text.pdf.PdfAction) */
2621     public void setPageAction(PdfName actionType, PdfAction action) throws DocumentException {
2622           if (!actionType.equals(PAGE_OPEN) && !actionType.equals(PAGE_CLOSE))
2623               throw new DocumentException("Invalid page additional action type: " + actionType.toString());
2624           pdf.setPageAction(actionType, action);
2625       }
2626
2627     /** @see com.lowagie.text.pdf.interfaces.PdfPageActions#setDuration(int) */
2628     public void setDuration(int seconds) {
2629          pdf.setDuration(seconds);
2630      }
2631
2632     /** @see com.lowagie.text.pdf.interfaces.PdfPageActions#setTransition(com.lowagie.text.pdf.PdfTransition) */
2633     public void setTransition(PdfTransition transition) {
2634          pdf.setTransition(transition);
2635      }
2636
2637 //  [U4] Thumbnail image
2638
2639     /**
2640      * Use this method to set the thumbnail image for the current page.
2641      * @param image the image
2642      * @throws PdfException on error
2643      * @throws DocumentException or error
2644      */

2645     public void setThumbnail(Image image) throws PdfException, DocumentException {
2646         pdf.setThumbnail(image);
2647     }
2648
2649 //  [U5] Transparency groups
2650
2651     /**
2652      * A group attributes dictionary specifying the attributes
2653      * of the page's page group for use in the transparent
2654      * imaging model
2655      */

2656     protected PdfDictionary group;
2657
2658     /**
2659      * Use this method to get the group dictionary.
2660      * @return Value of property group.
2661      */

2662     public PdfDictionary getGroup() {
2663         return this.group;
2664     }
2665
2666     /**
2667      * Use this method to set the group dictionary.
2668      * @param group New value of property group.
2669      */

2670     public void setGroup(PdfDictionary group) {
2671         this.group = group;
2672     }
2673
2674 //  [U6] space char ratio
2675
2676     /** The default space-char ratio. */
2677     public static final float SPACE_CHAR_RATIO_DEFAULT = 2.5f;
2678     /** Disable the inter-character spacing. */
2679     public static final float NO_SPACE_CHAR_RATIO = 10000000f;
2680
2681     /**
2682      * The ratio between the extra word spacing and the extra character spacing.
2683      * Extra word spacing will grow <CODE>ratio</CODE> times more than extra character spacing.
2684      */

2685     private float spaceCharRatio = SPACE_CHAR_RATIO_DEFAULT;
2686
2687     /**
2688      * Use this method to gets the space/character extra spacing ratio
2689      * for fully justified text.
2690      * @return the space/character extra spacing ratio
2691      */

2692     public float getSpaceCharRatio() {
2693         return spaceCharRatio;
2694     }
2695
2696     /**
2697      * Use this method to set the ratio between the extra word spacing and
2698      * the extra character spacing when the text is fully justified.
2699      * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more
2700      * than extra character spacing. If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE>
2701      * then the extra character spacing will be zero.
2702      * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
2703      */

2704     public void setSpaceCharRatio(float spaceCharRatio) {
2705         if (spaceCharRatio < 0.001f)
2706             this.spaceCharRatio = 0.001f;
2707         else
2708             this.spaceCharRatio = spaceCharRatio;
2709     }
2710
2711 //  [U7] run direction (doesn't actually do anything)
2712
2713     /** Use the default run direction. */
2714     public static final int RUN_DIRECTION_DEFAULT = 0;
2715     /** Do not use bidirectional reordering. */
2716     public static final int RUN_DIRECTION_NO_BIDI = 1;
2717     /** Use bidirectional reordering with left-to-right
2718      * preferential run direction.
2719      */

2720     public static final int RUN_DIRECTION_LTR = 2;
2721     /** Use bidirectional reordering with right-to-left
2722      * preferential run direction.
2723      */

2724     public static final int RUN_DIRECTION_RTL = 3;
2725
2726     protected int runDirection = RUN_DIRECTION_NO_BIDI;
2727
2728     /**
2729      * Use this method to set the run direction.
2730      * This is only used as a placeholder as it does not affect anything.
2731      * @param runDirection the run direction
2732      */

2733     public void setRunDirection(int runDirection) {
2734         if (runDirection < RUN_DIRECTION_NO_BIDI || runDirection > RUN_DIRECTION_RTL)
2735             throw new RuntimeException("Invalid run direction: " + runDirection);
2736         this.runDirection = runDirection;
2737     }
2738
2739     /**
2740      * Use this method to set the run direction.
2741      * @return the run direction
2742      */

2743     public int getRunDirection() {
2744         return runDirection;
2745     }
2746
2747 //  [U8] user units
2748
2749      protected float userunit = 0f;
2750     /**
2751      * Use this method to get the user unit.
2752      * A user unit is a value that defines the default user space unit.
2753      * The minimum UserUnit is 1 (1 unit = 1/72 inch).
2754      * The maximum UserUnit is 75,000.
2755      * Note that this userunit only works starting with PDF1.6!
2756      * @return Returns the userunit.
2757      */

2758     public float getUserunit() {
2759         return userunit;
2760     }
2761     /**
2762      * Use this method to set the user unit.
2763      * A UserUnit is a value that defines the default user space unit.
2764      * The minimum UserUnit is 1 (1 unit = 1/72 inch).
2765      * The maximum UserUnit is 75,000.
2766      * Note that this userunit only works starting with PDF1.6!
2767      * @param userunit The userunit to set.
2768      * @throws DocumentException on error
2769      */

2770      public void setUserunit(float userunit) throws DocumentException {
2771          if (userunit < 1f || userunit > 75000f) throw new DocumentException("UserUnit should be a value between 1 and 75000.");
2772          this.userunit = userunit;
2773          setAtLeastPdfVersion(VERSION_1_6);
2774      }
2775
2776 // Miscellaneous topics
2777
2778 //  [M1] Color settings
2779
2780     protected PdfDictionary defaultColorspace = new PdfDictionary();
2781     /**
2782      * Use this method to get the default colorspaces.
2783      * @return the default colorspaces
2784      */

2785     public PdfDictionary getDefaultColorspace() {
2786         return defaultColorspace;
2787     }
2788
2789     /**
2790      * Use this method to sets the default colorspace that will be applied
2791      * to all the document. The colorspace is only applied if another colorspace
2792      * with the same name is not present in the content.
2793      * <p>
2794      * The colorspace is applied immediately when creating templates and
2795      * at the page end for the main document content.
2796      * @param key the name of the colorspace. It can be <CODE>PdfName.DEFAULTGRAY</CODE>, <CODE>PdfName.DEFAULTRGB</CODE>
2797      * or <CODE>PdfName.DEFAULTCMYK</CODE>
2798      * @param cs the colorspace. A <CODE>null</CODE> or <CODE>PdfNull</CODE> removes any colorspace with the same name
2799      */

2800     public void setDefaultColorspace(PdfName key, PdfObject cs) {
2801         if (cs == null || cs.isNull())
2802             defaultColorspace.remove(key);
2803         defaultColorspace.put(key, cs);
2804     }
2805
2806 //  [M2] spot patterns
2807
2808     protected HashMap documentSpotPatterns = new HashMap();
2809     protected ColorDetails patternColorspaceRGB;
2810     protected ColorDetails patternColorspaceGRAY;
2811     protected ColorDetails patternColorspaceCMYK;
2812
2813     ColorDetails addSimplePatternColorspace(Color color) {
2814         int type = ExtendedColor.getType(color);
2815         if (type == ExtendedColor.TYPE_PATTERN || type == ExtendedColor.TYPE_SHADING)
2816             throw new RuntimeException("An uncolored tile pattern can not have another pattern or shading as color.");
2817         try {
2818             switch (type) {
2819                 case ExtendedColor.TYPE_RGB:
2820                     if (patternColorspaceRGB == null) {
2821                         patternColorspaceRGB = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null);
2822                         PdfArray array = new PdfArray(PdfName.PATTERN);
2823                         array.add(PdfName.DEVICERGB);
2824                         addToBody(array, patternColorspaceRGB.getIndirectReference());
2825                     }
2826                     return patternColorspaceRGB;
2827                 case ExtendedColor.TYPE_CMYK:
2828                     if (patternColorspaceCMYK == null) {
2829                         patternColorspaceCMYK = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null);
2830                         PdfArray array = new PdfArray(PdfName.PATTERN);
2831                         array.add(PdfName.DEVICECMYK);
2832                         addToBody(array, patternColorspaceCMYK.getIndirectReference());
2833                     }
2834                     return patternColorspaceCMYK;
2835                 case ExtendedColor.TYPE_GRAY:
2836                     if (patternColorspaceGRAY == null) {
2837                         patternColorspaceGRAY = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null);
2838                         PdfArray array = new PdfArray(PdfName.PATTERN);
2839                         array.add(PdfName.DEVICEGRAY);
2840                         addToBody(array, patternColorspaceGRAY.getIndirectReference());
2841                     }
2842                     return patternColorspaceGRAY;
2843                 case ExtendedColor.TYPE_SEPARATION: {
2844                     ColorDetails details = addSimple(((SpotColor)color).getPdfSpotColor());
2845                     ColorDetails patternDetails = (ColorDetails)documentSpotPatterns.get(details);
2846                     if (patternDetails == null) {
2847                         patternDetails = new ColorDetails(getColorspaceName(), body.getPdfIndirectReference(), null);
2848                         PdfArray array = new PdfArray(PdfName.PATTERN);
2849                         array.add(details.getIndirectReference());
2850                         addToBody(array, patternDetails.getIndirectReference());
2851                         documentSpotPatterns.put(details, patternDetails);
2852                     }
2853                     return patternDetails;
2854                 }
2855                 default:
2856                     throw new RuntimeException("Invalid color type in PdfWriter.addSimplePatternColorspace().");
2857             }
2858         }
2859         catch (Exception e) {
2860             throw new RuntimeException(e.getMessage());
2861         }
2862     }
2863
2864 //  [M3] Images
2865
2866     /**
2867      * Use this method to get the strictImageSequence status.
2868      * @return value of property strictImageSequence
2869      */

2870     public boolean isStrictImageSequence() {
2871         return pdf.isStrictImageSequence();
2872     }
2873
2874     /**
2875      * Use this method to set the image sequence, so that it follows
2876      * the text in strict order (or not).
2877      * @param strictImageSequence new value of property strictImageSequence
2878      *
2879      */

2880     public void setStrictImageSequence(boolean strictImageSequence) {
2881         pdf.setStrictImageSequence(strictImageSequence);
2882     }
2883
2884     /**
2885      * Use this method to clear text wrapping around images (if applicable).
2886      * @throws DocumentException
2887      */

2888     public void clearTextWrap() throws DocumentException {
2889         pdf.clearTextWrap();
2890     }
2891
2892     /** Dictionary, containing all the images of the PDF document */
2893     protected PdfDictionary imageDictionary = new PdfDictionary();
2894
2895     /** This is the list with all the images in the document. */
2896     private HashMap images = new HashMap();
2897
2898     /**
2899      * Use this method to adds an image to the document
2900      * but not to the page resources. It is used with
2901      * templates and <CODE>Document.add(Image)</CODE>.
2902      * Use this method only if you know what you're doing!
2903      * @param image the <CODE>Image</CODE> to add
2904      * @return the name of the image added
2905      * @throws PdfException on error
2906      * @throws DocumentException on error
2907      */

2908     public PdfName addDirectImageSimple(Image image) throws PdfException, DocumentException {
2909         return addDirectImageSimple(image, null);
2910     }
2911
2912     /**
2913      * Adds an image to the document but not to the page resources.
2914      * It is used with templates and <CODE>Document.add(Image)</CODE>.
2915      * Use this method only if you know what you're doing!
2916      * @param image the <CODE>Image</CODE> to add
2917      * @param fixedRef the reference to used. It may be <CODE>null</CODE>,
2918      * a <CODE>PdfIndirectReference</CODE> or a <CODE>PRIndirectReference</CODE>.
2919      * @return the name of the image added
2920      * @throws PdfException on error
2921      * @throws DocumentException on error
2922      */

2923     public PdfName addDirectImageSimple(Image image, PdfIndirectReference fixedRef) throws PdfException, DocumentException {
2924         PdfName name;
2925         // if the images is already added, just retrieve the name
2926         if (images.containsKey(image.getMySerialId())) {
2927             name = (PdfName) images.get(image.getMySerialId());
2928         }
2929         // if it's a new image, add it to the document
2930         else {
2931             if (image.isImgTemplate()) {
2932                 name = new PdfName("img" + images.size());
2933                 if(image instanceof ImgWMF){
2934                     try {
2935                         ImgWMF wmf = (ImgWMF)image;
2936                         wmf.readWMF(PdfTemplate.createTemplate(this, 0, 0));
2937                     }
2938                     catch (Exception e) {
2939                         throw new DocumentException(e);
2940                     }
2941                 }
2942             }
2943             else {
2944                 PdfIndirectReference dref = image.getDirectReference();
2945                 if (dref != null) {
2946                     PdfName rname = new PdfName("img" + images.size());
2947                     images.put(image.getMySerialId(), rname);
2948                     imageDictionary.put(rname, dref);
2949                     return rname;
2950                 }
2951                 Image maskImage = image.getImageMask();
2952                 PdfIndirectReference maskRef = null;
2953                 if (maskImage != null) {
2954                     PdfName mname = (PdfName)images.get(maskImage.getMySerialId());
2955                     maskRef = getImageReference(mname);
2956                 }
2957                 PdfImage i = new PdfImage(image, "img" + images.size(), maskRef);
2958                 if (image instanceof ImgJBIG2) {
2959                     byte[] globals = ((ImgJBIG2) image).getGlobalBytes();
2960                     if (globals != null) {
2961                         PdfDictionary decodeparms = new PdfDictionary();
2962                         decodeparms.put(PdfName.JBIG2GLOBALS, getReferenceJBIG2Globals(globals));
2963                         i.put(PdfName.DECODEPARMS, decodeparms);
2964                     }
2965                 }
2966                 if (image.hasICCProfile()) {
2967                     PdfICCBased icc = new PdfICCBased(image.getICCProfile(), image.getCompressionLevel());
2968                     PdfIndirectReference iccRef = add(icc);
2969                     PdfArray iccArray = new PdfArray();
2970                     iccArray.add(PdfName.ICCBASED);
2971                     iccArray.add(iccRef);
2972                     PdfArray colorspace = i.getAsArray(PdfName.COLORSPACE);
2973                     if (colorspace != null) {
2974                         if (colorspace.size() > 1 && PdfName.INDEXED.equals(colorspace.getPdfObject(0)))
2975                             colorspace.set(1, iccArray);
2976                         else
2977                             i.put(PdfName.COLORSPACE, iccArray);
2978                     }
2979                     else
2980                         i.put(PdfName.COLORSPACE, iccArray);
2981                 }
2982                 add(i, fixedRef);
2983                 name = i.name();
2984             }
2985             images.put(image.getMySerialId(), name);
2986         }
2987         return name;
2988     }
2989
2990     /**
2991      * Writes a <CODE>PdfImage</CODE> to the outputstream.
2992      *
2993      * @param pdfImage the image to be added
2994      * @return a <CODE>PdfIndirectReference</CODE> to the encapsulated image
2995      * @throws PdfException when a document isn't open yet, or has been closed
2996      */

2997
2998     PdfIndirectReference add(PdfImage pdfImage, PdfIndirectReference fixedRef) throws PdfException {
2999         if (! imageDictionary.contains(pdfImage.name())) {
3000             PdfXConformanceImp.checkPDFXConformance(this, PdfXConformanceImp.PDFXKEY_IMAGE, pdfImage);
3001             if (fixedRef instanceof PRIndirectReference) {
3002                 PRIndirectReference r2 = (PRIndirectReference)fixedRef;
3003                 fixedRef = new PdfIndirectReference(0, getNewObjectNumber(r2.getReader(), r2.getNumber(), r2.getGeneration()));
3004             }
3005             try {
3006                 if (fixedRef == null)
3007                     fixedRef = addToBody(pdfImage).getIndirectReference();
3008                 else
3009                     addToBody(pdfImage, fixedRef);
3010             }
3011             catch(IOException ioe) {
3012                 throw new ExceptionConverter(ioe);
3013             }
3014             imageDictionary.put(pdfImage.name(), fixedRef);
3015             return fixedRef;
3016         }
3017         return (PdfIndirectReference) imageDictionary.get(pdfImage.name());
3018     }
3019
3020     /**
3021      * return the <CODE>PdfIndirectReference</CODE> to the image with a given name.
3022      *
3023      * @param name the name of the image
3024      * @return a <CODE>PdfIndirectReference</CODE>
3025      */

3026
3027     PdfIndirectReference getImageReference(PdfName name) {
3028         return (PdfIndirectReference) imageDictionary.get(name);
3029     }
3030
3031     protected PdfIndirectReference add(PdfICCBased icc) {
3032         PdfIndirectObject object;
3033         try {
3034             object = addToBody(icc);
3035         }
3036         catch(IOException ioe) {
3037             throw new ExceptionConverter(ioe);
3038         }
3039         return object.getIndirectReference();
3040     }
3041
3042     /**
3043      * A HashSet with Stream objects containing JBIG2 Globals
3044      * @since 2.1.5
3045      */

3046     protected HashMap JBIG2Globals = new HashMap();
3047     /**
3048      * Gets an indirect reference to a JBIG2 Globals stream.
3049      * Adds the stream if it hasn't already been added to the writer.
3050      * @param    content a byte array that may already been added to the writer inside a stream object.
3051      * @since  2.1.5
3052      */

3053     protected PdfIndirectReference getReferenceJBIG2Globals(byte[] content) {
3054         if (content == nullreturn null;
3055         PdfStream stream;
3056         for (Iterator i = JBIG2Globals.keySet().iterator(); i.hasNext(); ) {
3057             stream = (PdfStream) i.next();
3058             if (Arrays.equals(content, stream.getBytes())) {
3059                 return (PdfIndirectReference) JBIG2Globals.get(stream);
3060             }
3061         }
3062         stream = new PdfStream(content);
3063         PdfIndirectObject ref;
3064         try {
3065             ref = addToBody(stream);
3066         } catch (IOException e) {
3067             return null;
3068         }
3069         JBIG2Globals.put(stream, ref.getIndirectReference());
3070         return ref.getIndirectReference();
3071     }
3072
3073 //  [M4] Old table functionality; do we still need it?
3074
3075     /**
3076      * Checks if a <CODE>Table</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
3077      *
3078      * @param   table   the table that has to be checked
3079      * @param   margin  a certain margin
3080      * @return  <CODE>true</CODE> if the <CODE>Table</CODE> fits the page, <CODE>false</CODE> otherwise.
3081      */

3082
3083     public boolean fitsPage(Table table, float margin) {
3084         return pdf.bottom(table) > pdf.indentBottom() + margin;
3085     }
3086
3087     /**
3088      * Checks if a <CODE>Table</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
3089      *
3090      * @param   table  the table that has to be checked
3091      * @return  <CODE>true</CODE> if the <CODE>Table</CODE> fits the page, <CODE>false</CODE> otherwise.
3092      */

3093
3094     public boolean fitsPage(Table table) {
3095         return fitsPage(table, 0);
3096     }
3097 //  [F12] tagged PDF
3098     /**
3099      * A flag indicating the presence of structure elements that contain user properties attributes.
3100      */

3101     private boolean userProperties;
3102
3103     /**
3104      * Gets the flag indicating the presence of structure elements that contain user properties attributes.
3105      * @return the user properties flag
3106      */

3107     public boolean isUserProperties() {
3108         return this.userProperties;
3109     }
3110
3111     /**
3112      * Sets the flag indicating the presence of structure elements that contain user properties attributes.
3113      * @param userProperties the user properties flag
3114      */

3115     public void setUserProperties(boolean userProperties) {
3116         this.userProperties = userProperties;
3117     }
3118
3119     /**
3120      * Holds value of property RGBTranparency.
3121      */

3122     private boolean rgbTransparencyBlending;
3123
3124     /**
3125      * Gets the transparency blending colorspace.
3126      * @return <code>true</code> if the transparency blending colorspace is RGB, <code>false</code>
3127      * if it is the default blending colorspace
3128      * @since 2.1.0
3129      */

3130     public boolean isRgbTransparencyBlending() {
3131         return this.rgbTransparencyBlending;
3132     }
3133
3134     /**
3135      * Sets the transparency blending colorspace to RGB. The default blending colorspace is
3136      * CMYK and will result in faded colors in the screen and in printing. Calling this method
3137      * will return the RGB colors to what is expected. The RGB blending will be applied to all subsequent pages
3138      * until other value is set.
3139      * Note that this is a generic solution that may not work in all cases.
3140      * @param rgbTransparencyBlending <code>true</code> to set the transparency blending colorspace to RGB, <code>false</code>
3141      * to use the default blending colorspace
3142      * @since 2.1.0
3143      */

3144     public void setRgbTransparencyBlending(boolean rgbTransparencyBlending) {
3145         this.rgbTransparencyBlending = rgbTransparencyBlending;
3146     }
3147 }
3148