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