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