1 /*
2 * $Id: PdfWriter.java,v 1.96 2005/05/04 14:32:39 blowagie Exp $
3 * $Name: $
4 *
5 * Copyright 1999, 2000, 2001, 2002 Bruno Lowagie
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * (the "License"); you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the License.
14 *
15 * The Original Code is 'iText, a free JAVA-PDF library'.
16 *
17 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
18 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
19 * All Rights Reserved.
20 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
21 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
22 *
23 * Contributor(s): all the names of the contributors are added in the source code
24 * where applicable.
25 *
26 * Alternatively, the contents of this file may be used under the terms of the
27 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
28 * provisions of LGPL are applicable instead of those above. If you wish to
29 * allow use of your version of this file only under the terms of the LGPL
30 * License and not to allow others to use your version of this file under
31 * the MPL, indicate your decision by deleting the provisions above and
32 * replace them with the notice and other provisions required by the LGPL.
33 * If you do not delete the provisions above, a recipient may use your version
34 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
35 *
36 * This library is free software; you can redistribute it and/or modify it
37 * under the terms of the MPL as stated above or under the terms of the GNU
38 * Library General Public License as published by the Free Software Foundation;
39 * either version 2 of the License, or any later version.
40 *
41 * This library is distributed in the hope that it will be useful, but WITHOUT
42 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
43 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
44 * details.
45 *
46 * If you didn't download this code from the following link, you should check if
47 * you aren't using an obsolete version:
48 * http://www.lowagie.com/iText/
49 */
50
51 package com.lowagie.text.pdf;
52
53 import java.awt.Color;
54 import java.io.IOException;
55 import java.io.OutputStream;
56 import java.util.ArrayList;
57 import java.util.HashMap;
58 import java.util.Iterator;
59 import java.util.TreeMap;
60 import java.util.TreeSet;
61 import java.util.HashSet;
62
63 import com.lowagie.text.DocListener;
64 import com.lowagie.text.DocWriter;
65 import com.lowagie.text.Document;
66 import com.lowagie.text.DocumentException;
67 import com.lowagie.text.ExceptionConverter;
68 import com.lowagie.text.Image;
69 import com.lowagie.text.ImgWMF;
70 import com.lowagie.text.Rectangle;
71 import com.lowagie.text.Table;
72 import com.lowagie.text.ImgPostscript;
73
74 /**
75 * A <CODE>DocWriter</CODE> class for PDF.
76 * <P>
77 * When this <CODE>PdfWriter</CODE> is added
78 * to a certain <CODE>PdfDocument</CODE>, the PDF representation of every Element
79 * added to this Document will be written to the outputstream.</P>
80 */
81
82 public class PdfWriter extends DocWriter {
83
84 // inner classes
85
86 /**
87 * This class generates the structure of a PDF document.
88 * <P>
89 * This class covers the third section of Chapter 5 in the 'Portable Document Format
90 * Reference Manual version 1.3' (page 55-60). It contains the body of a PDF document
91 * (section 5.14) and it can also generate a Cross-reference Table (section 5.15).
92 *
93 * @see PdfWriter
94 * @see PdfObject
95 * @see PdfIndirectObject
96 */
97
98 public static class PdfBody {
99
100 // inner classes
101
102 /**
103 * <CODE>PdfCrossReference</CODE> is an entry in the PDF Cross-Reference table.
104 */
105
106 static class PdfCrossReference implements Comparable {
107
108 // membervariables
109 private int type;
110
111 /** Byte offset in the PDF file. */
112 private int offset;
113
114 private int refnum;
115 /** generation of the object. */
116 private int generation;
117
118 // constructors
119 /**
120 * Constructs a cross-reference element for a PdfIndirectObject.
121 * @param refnum
122 * @param offset byte offset of the object
123 * @param generation generationnumber of the object
124 */
125
126 PdfCrossReference(int refnum, int offset, int generation) {
127 type = 0;
128 this.offset = offset;
129 this.refnum = refnum;
130 this.generation = generation;
131 }
132
133 /**
134 * Constructs a cross-reference element for a PdfIndirectObject.
135 * @param refnum
136 * @param offset byte offset of the object
137 */
138
139 PdfCrossReference(int refnum, int offset) {
140 type = 1;
141 this.offset = offset;
142 this.refnum = refnum;
143 this.generation = 0;
144 }
145
146 PdfCrossReference(int type, int refnum, int offset, int generation) {
147 this.type = type;
148 this.offset = offset;
149 this.refnum = refnum;
150 this.generation = generation;
151 }
152
153 int getRefnum() {
154 return refnum;
155 }
156
157 /**
158 * Returns the PDF representation of this <CODE>PdfObject</CODE>.
159 * @param os
160 * @throws IOException
161 */
162
163 public void toPdf(OutputStream os) throws IOException {
164 // This code makes it more difficult to port the lib to JDK1.1.x:
165 // StringBuffer off = new StringBuffer("0000000000").append(offset);
166 // off.delete(0, off.length() - 10);
167 // StringBuffer gen = new StringBuffer("00000").append(generation);
168 // gen.delete(0, gen.length() - 5);
169 // so it was changed into this:
170 String s = "0000000000" + offset;
171 StringBuffer off = new StringBuffer(s.substring(s.length() - 10));
172 s = "00000" + generation;
173 String gen = s.substring(s.length() - 5);
174 if (generation == 65535) {
175 os.write(getISOBytes(off.append(' ').append(gen).append(" f \n").toString()));
176 }
177 else
178 os.write(getISOBytes(off.append(' ').append(gen).append(" n \n").toString()));
179 }
180
181 /**
182 * Writes PDF syntax to the OutputStream
183 * @param midSize
184 * @param os
185 * @throws IOException
186 */
187 public void toPdf(int midSize, OutputStream os) throws IOException {
188 os.write((byte)type);
189 while (--midSize >= 0)
190 os.write((byte)((offset >>> (8 * midSize)) & 0xff));
191 os.write((byte)((generation >>> 8) & 0xff));
192 os.write((byte)(generation & 0xff));
193 }
194
195 /**
196 * @see java.lang.Comparable#compareTo(java.lang.Object)
197 */
198 public int compareTo(Object o) {
199 PdfCrossReference other = (PdfCrossReference)o;
200 return (refnum < other.refnum ? -1 : (refnum==other.refnum ? 0 : 1));
201 }
202
203 /**
204 * @see java.lang.Object#equals(java.lang.Object)
205 */
206 public boolean equals(Object obj) {
207 if (obj instanceof PdfCrossReference) {
208 PdfCrossReference other = (PdfCrossReference)obj;
209 return (refnum == other.refnum);
210 }
211 else
212 return false;
213 }
214
215 }
216
217 // membervariables
218
219 /** array containing the cross-reference table of the normal objects. */
220 private TreeSet xrefs;
221 private int refnum;
222 /** the current byteposition in the body. */
223 private int position;
224 private PdfWriter writer;
225 // constructors
226
227 /**
228 * Constructs a new <CODE>PdfBody</CODE>.
229 * @param writer
230 */
231 PdfBody(PdfWriter writer) {
232 xrefs = new TreeSet();
233 xrefs.add(new PdfCrossReference(0, 0, 65535));
234 position = writer.getOs().getCounter();
235 refnum = 1;
236 this.writer = writer;
237 }
238
239 void setRefnum(int refnum) {
240 this.refnum = refnum;
241 }
242
243 // methods
244
245 private static final int OBJSINSTREAM = 200;
246
247 private ByteBuffer index;
248 private ByteBuffer streamObjects;
249 private int currentObjNum;
250 private int numObj = 0;
251
252 private PdfWriter.PdfBody.PdfCrossReference addToObjStm(PdfObject obj, int nObj) throws IOException {
253 if (numObj >= OBJSINSTREAM)
254 flushObjStm();
255 if (index == null) {
256 index = new ByteBuffer();
257 streamObjects = new ByteBuffer();
258 currentObjNum = getIndirectReferenceNumber();
259 numObj = 0;
260 }
261 int p = streamObjects.size();
262 int idx = numObj++;
263 PdfEncryption enc = writer.crypto;
264 writer.crypto = null;
265 obj.toPdf(writer, streamObjects);
266 writer.crypto = enc;
267 streamObjects.append(' ');
268 index.append(nObj).append(' ').append(p).append(' ');
269 return new PdfWriter.PdfBody.PdfCrossReference(2, nObj, currentObjNum, idx);
270 }
271
272 private void flushObjStm() throws IOException {
273 if (numObj == 0)
274 return;
275 int first = index.size();
276 index.append(streamObjects);
277 PdfStream stream = new PdfStream(index.toByteArray());
278 stream.flateCompress();
279 stream.put(PdfName.TYPE, PdfName.OBJSTM);
280 stream.put(PdfName.N, new PdfNumber(numObj));
281 stream.put(PdfName.FIRST, new PdfNumber(first));
282 add(stream, currentObjNum);
283 index = null;
284 streamObjects = null;
285 numObj = 0;
286 }
287
288 /**
289 * Adds a <CODE>PdfObject</CODE> to the body.
290 * <P>
291 * This methods creates a <CODE>PdfIndirectObject</CODE> with a
292 * certain number, containing the given <CODE>PdfObject</CODE>.
293 * It also adds a <CODE>PdfCrossReference</CODE> for this object
294 * to an <CODE>ArrayList</CODE> that will be used to build the
295 * Cross-reference Table.
296 *
297 * @param object a <CODE>PdfObject</CODE>
298 * @return a <CODE>PdfIndirectObject</CODE>
299 * @throws IOException
300 */
301
302 PdfIndirectObject add(PdfObject object) throws IOException {
303 return add(object, getIndirectReferenceNumber());
304 }
305
306 PdfIndirectObject add(PdfObject object, boolean inObjStm) throws IOException {
307 return add(object, getIndirectReferenceNumber(), inObjStm);
308 }
309
310 /**
311 * Gets a PdfIndirectReference for an object that will be created in the future.
312 * @return a PdfIndirectReference
313 */
314
315 PdfIndirectReference getPdfIndirectReference() {
316 return new PdfIndirectReference(0, getIndirectReferenceNumber());
317 }
318
319 int getIndirectReferenceNumber() {
320 int n = refnum++;
321 xrefs.add(new PdfCrossReference(n, 0, 65536));
322 return n;
323 }
324
325 /**
326 * Adds a <CODE>PdfObject</CODE> to the body given an already existing
327 * PdfIndirectReference.
328 * <P>
329 * This methods creates a <CODE>PdfIndirectObject</CODE> with the number given by
330 * <CODE>ref</CODE>, containing the given <CODE>PdfObject</CODE>.
331 * It also adds a <CODE>PdfCrossReference</CODE> for this object
332 * to an <CODE>ArrayList</CODE> that will be used to build the
333 * Cross-reference Table.
334 *
335 * @param object a <CODE>PdfObject</CODE>
336 * @param ref a <CODE>PdfIndirectReference</CODE>
337 * @return a <CODE>PdfIndirectObject</CODE>
338 * @throws IOException
339 */
340
341 PdfIndirectObject add(PdfObject object, PdfIndirectReference ref) throws IOException {
342 return add(object, ref.getNumber());
343 }
344
345 PdfIndirectObject add(PdfObject object, PdfIndirectReference ref, boolean inObjStm) throws IOException {
346 return add(object, ref.getNumber(), inObjStm);
347 }
348
349 PdfIndirectObject add(PdfObject object, int refNumber) throws IOException {
350 return add(object, refNumber, true); // to false
351 }
352
353 PdfIndirectObject add(PdfObject object, int refNumber, boolean inObjStm) throws IOException {
354 if (inObjStm && object.canBeInObjStm() && writer.isFullCompression()) {
355 PdfCrossReference pxref = addToObjStm(object, refNumber);
356 PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer);
357 if (!xrefs.add(pxref)) {
358 xrefs.remove(pxref);
359 xrefs.add(pxref);
360 }
361 return indirect;
362 }
363 else {
364 PdfIndirectObject indirect = new PdfIndirectObject(refNumber, object, writer);
365 PdfCrossReference pxref = new PdfCrossReference(refNumber, position);
366 if (!xrefs.add(pxref)) {
367 xrefs.remove(pxref);
368 xrefs.add(pxref);
369 }
370 indirect.writeTo(writer.getOs());
371 position = writer.getOs().getCounter();
372 return indirect;
373 }
374 }
375
376 /**
377 * Adds a <CODE>PdfResources</CODE> object to the body.
378 *
379 * @param object the <CODE>PdfResources</CODE>
380 * @return a <CODE>PdfIndirectObject</CODE>
381 */
382
383 // PdfIndirectObject add(PdfResources object) {
384 // return add(object);
385 // }
386
387 /**
388 * Adds a <CODE>PdfPages</CODE> object to the body.
389 *
390 * @param object the root of the document
391 * @return a <CODE>PdfIndirectObject</CODE>
392 */
393
394 // PdfIndirectObject add(PdfPages object) throws IOException {
395 // PdfIndirectObject indirect = new PdfIndirectObject(PdfWriter.ROOT, object, writer);
396 // rootOffset = position;
397 // indirect.writeTo(writer.getOs());
398 // position = writer.getOs().getCounter();
399 // return indirect;
400 // }
401
402 /**
403 * Returns the offset of the Cross-Reference table.
404 *
405 * @return an offset
406 */
407
408 int offset() {
409 return position;
410 }
411
412 /**
413 * Returns the total number of objects contained in the CrossReferenceTable of this <CODE>Body</CODE>.
414 *
415 * @return a number of objects
416 */
417
418 int size() {
419 return Math.max(((PdfCrossReference)xrefs.last()).getRefnum() + 1, refnum);
420 }
421
422 /**
423 * Returns the CrossReferenceTable of the <CODE>Body</CODE>.
424 * @param os
425 * @param root
426 * @param info
427 * @param encryption
428 * @param fileID
429 * @param prevxref
430 * @throws IOException
431 */
432
433 void writeCrossReferenceTable(OutputStream os, PdfIndirectReference root, PdfIndirectReference info, PdfIndirectReference encryption, PdfObject fileID, int prevxref) throws IOException {
434 int refNumber = 0;
435 if (writer.isFullCompression()) {
436 flushObjStm();
437 refNumber = getIndirectReferenceNumber();
438 xrefs.add(new PdfCrossReference(refNumber, position));
439 }
440 PdfCrossReference entry = (PdfCrossReference)xrefs.first();
441 int first = entry.getRefnum();
442 int len = 0;
443 ArrayList sections = new ArrayList();
444 for (Iterator i = xrefs.iterator(); i.hasNext(); ) {
445 entry = (PdfCrossReference)i.next();
446 if (first + len == entry.getRefnum())
447 ++len;
448 else {
449 sections.add(new Integer(first));
450 sections.add(new Integer(len));
451 first = entry.getRefnum();
452 len = 1;
453 }
454 }
455 sections.add(new Integer(first));
456 sections.add(new Integer(len));
457 if (writer.isFullCompression()) {
458 int mid = 4;
459 int mask = 0xff000000;
460 for (; mid > 1; --mid) {
461 if ((mask & position) != 0)
462 break;
463 mask >>>= 8;
464 }
465 ByteBuffer buf = new ByteBuffer();
466
467 for (Iterator i = xrefs.iterator(); i.hasNext(); ) {
468 entry = (PdfCrossReference) i.next();
469 entry.toPdf(mid, buf);
470 }
471 PdfStream xr = new PdfStream(buf.toByteArray());
472 buf = null;
473 xr.flateCompress();
474 xr.put(PdfName.SIZE, new PdfNumber(size()));
475 xr.put(PdfName.ROOT, root);
476 if (info != null) {
477 xr.put(PdfName.INFO, info);
478 }
479 if (encryption != null)
480 xr.put(PdfName.ENCRYPT, encryption);
481 if (fileID != null)
482 xr.put(PdfName.ID, fileID);
483 xr.put(PdfName.W, new PdfArray(new int[]{1, mid, 2}));
484 xr.put(PdfName.TYPE, PdfName.XREF);
485 PdfArray idx = new PdfArray();
486 for (int k = 0; k < sections.size(); ++k)
487 idx.add(new PdfNumber(((Integer)sections.get(k)).intValue()));
488 xr.put(PdfName.INDEX, idx);
489 if (prevxref > 0)
490 xr.put(PdfName.PREV, new PdfNumber(prevxref));
491 PdfEncryption enc = writer.crypto;
492 writer.crypto = null;
493 PdfIndirectObject indirect = new PdfIndirectObject(refNumber, xr, writer);
494 indirect.writeTo(writer.getOs());
495 writer.crypto = enc;
496 }
497 else {
498 os.write(getISOBytes("xref\n"));
499 Iterator i = xrefs.iterator();
500 for (int k = 0; k < sections.size(); k += 2) {
501 first = ((Integer)sections.get(k)).intValue();
502 len = ((Integer)sections.get(k + 1)).intValue();
503 os.write(getISOBytes(String.valueOf(first)));
504 os.write(getISOBytes(" "));
505 os.write(getISOBytes(String.valueOf(len)));
506 os.write('\n');
507 while (len-- > 0) {
508 entry = (PdfCrossReference) i.next();
509 entry.toPdf(os);
510 }
511 }
512 }
513 }
514 }
515
516 /**
517 * <CODE>PdfTrailer</CODE> is the PDF Trailer object.
518 * <P>
519 * This object is described in the 'Portable Document Format Reference Manual version 1.3'
520 * section 5.16 (page 59-60).
521 */
522
523 static class PdfTrailer extends PdfDictionary {
524
525 // membervariables
526
527 int offset;
528
529 // constructors
530
531 /**
532 * Constructs a PDF-Trailer.
533 *
534 * @param size the number of entries in the <CODE>PdfCrossReferenceTable</CODE>
535 * @param offset offset of the <CODE>PdfCrossReferenceTable</CODE>
536 * @param root an indirect reference to the root of the PDF document
537 * @param info an indirect reference to the info object of the PDF document
538 * @param encryption
539 * @param fileID
540 * @param prevxref
541 */
542
543 PdfTrailer(int size, int offset, PdfIndirectReference root, PdfIndirectReference info, PdfIndirectReference encryption, PdfObject fileID, int prevxref) {
544 this.offset = offset;
545 put(PdfName.SIZE, new PdfNumber(size));
546 put(PdfName.ROOT, root);
547 if (info != null) {
548 put(PdfName.INFO, info);
549 }
550 if (encryption != null)
551 put(PdfName.ENCRYPT, encryption);
552 if (fileID != null)
553 put(PdfName.ID, fileID);
554 if (prevxref > 0)
555 put(PdfName.PREV, new PdfNumber(prevxref));
556 }
557
558 /**
559 * Returns the PDF representation of this <CODE>PdfObject</CODE>.
560 * @param writer
561 * @param os
562 * @throws IOException
563 */
564 public void toPdf(PdfWriter writer, OutputStream os) throws IOException {
565 os.write(getISOBytes("trailer\n"));
566 super.toPdf(null, os);
567 os.write(getISOBytes("\nstartxref\n"));
568 os.write(getISOBytes(String.valueOf(offset)));
569 os.write(getISOBytes("\n%%EOF\n"));
570 }
571 }
572 // static membervariables
573
574 /** A viewer preference */
575 public static final int PageLayoutSinglePage = 1;
576 /** A viewer preference */
577 public static final int PageLayoutOneColumn = 2;
578 /** A viewer preference */
579 public static final int PageLayoutTwoColumnLeft = 4;
580 /** A viewer preference */
581 public static final int PageLayoutTwoColumnRight = 8;
582
583 /** A viewer preference */
584 public static final int PageModeUseNone = 16;
585 /** A viewer preference */
586 public static final int PageModeUseOutlines = 32;
587 /** A viewer preference */
588 public static final int PageModeUseThumbs = 64;
589 /** A viewer preference */
590 public static final int PageModeFullScreen = 128;
591 /** A viewer preference */
592 public static final int PageModeUseOC = 1 << 20;
593
594 /** A viewer preference */
595 public static final int HideToolbar = 256;
596 /** A viewer preference */
597 public static final int HideMenubar = 512;
598 /** A viewer preference */
599 public static final int HideWindowUI = 1024;
600 /** A viewer preference */
601 public static final int FitWindow = 2048;
602 /** A viewer preference */
603 public static final int CenterWindow = 4096;
604
605 /** A viewer preference */
606 public static final int NonFullScreenPageModeUseNone = 8192;
607 /** A viewer preference */
608 public static final int NonFullScreenPageModeUseOutlines = 16384;
609 /** A viewer preference */
610 public static final int NonFullScreenPageModeUseThumbs = 32768;
611 /** A viewer preference */
612 public static final int NonFullScreenPageModeUseOC = 1 << 19;
613
614 /** A viewer preference */
615 public static final int DirectionL2R = 1 << 16;
616 /** A viewer preference */
617 public static final int DirectionR2L = 1 << 17;
618 /** A viewer preference */
619 public static final int DisplayDocTitle = 1 << 18;
620 /** A viewer preference */
621 public static final int PrintScalingNone = 1 << 20;
622 /** The mask to decide if a ViewerPreferences dictionary is needed */
623 static final int ViewerPreferencesMask = 0xffff00;
624 /** The operation permitted when the document is opened with the user password */
625 public static final int AllowPrinting = 4 + 2048;
626 /** The operation permitted when the document is opened with the user password */
627 public static final int AllowModifyContents = 8;
628 /** The operation permitted when the document is opened with the user password */
629 public static final int AllowCopy = 16;
630 /** The operation permitted when the document is opened with the user password */
631 public static final int AllowModifyAnnotations = 32;
632 /** The operation permitted when the document is opened with the user password */
633 public static final int AllowFillIn = 256;
634 /** The operation permitted when the document is opened with the user password */
635 public static final int AllowScreenReaders = 512;
636 /** The operation permitted when the document is opened with the user password */
637 public static final int AllowAssembly = 1024;
638 /** The operation permitted when the document is opened with the user password */
639 public static final int AllowDegradedPrinting = 4;
640 /** Type of encryption */
641 public static final boolean STRENGTH40BITS = false;
642 /** Type of encryption */
643 public static final boolean STRENGTH128BITS = true;
644 /** action value */
645 public static final PdfName DOCUMENT_CLOSE = PdfName.WC;
646 /** action value */
647 public static final PdfName WILL_SAVE = PdfName.WS;
648 /** action value */
649 public static final PdfName DID_SAVE = PdfName.DS;
650 /** action value */
651 public static final PdfName WILL_PRINT = PdfName.WP;
652 /** action value */
653 public static final PdfName DID_PRINT = PdfName.DP;
654 /** action value */
655 public static final PdfName PAGE_OPEN = PdfName.O;
656 /** action value */
657 public static final PdfName PAGE_CLOSE = PdfName.C;
658
659 /** signature value */
660 public static final int SIGNATURE_EXISTS = 1;
661 /** signature value */
662 public static final int SIGNATURE_APPEND_ONLY = 2;
663
664 /** possible PDF version */
665 public static final char VERSION_1_2 = '2';
666 /** possible PDF version */
667 public static final char VERSION_1_3 = '3';
668 /** possible PDF version */
669 public static final char VERSION_1_4 = '4';
670 /** possible PDF version */
671 public static final char VERSION_1_5 = '5';
672 /** possible PDF version */
673 public static final char VERSION_1_6 = '6';
674
675 private static final int VPOINT = 7;
676 /** this is the header of a PDF document */
677 protected byte[] HEADER = getISOBytes("%PDF-1.4\n%\u00e2\u00e3\u00cf\u00d3\n");
678
679 protected int prevxref = 0;
680
681 protected PdfPages root = new PdfPages(this);
682
683 /** Dictionary, containing all the images of the PDF document */
684 protected PdfDictionary imageDictionary = new PdfDictionary();
685
686 /** This is the list with all the images in the document. */
687 private HashMap images = new HashMap();
688
689 /** The form XObjects in this document. The key is the xref and the value
690 is Object[]{PdfName, template}.*/
691 protected HashMap formXObjects = new HashMap();
692
693 /** The name counter for the form XObjects name. */
694 protected int formXObjectsCounter = 1;
695
696 /** The font number counter for the fonts in the document. */
697 protected int fontNumber = 1;
698
699 /** The color number counter for the colors in the document. */
700 protected int colorNumber = 1;
701
702 /** The patten number counter for the colors in the document. */
703 protected int patternNumber = 1;
704
705 /** The direct content in this document. */
706 protected PdfContentByte directContent;
707
708 /** The direct content under in this document. */
709 protected PdfContentByte directContentUnder;
710
711 /** The fonts of this document */
712 protected HashMap documentFonts = new HashMap();
713
714 /** The colors of this document */
715 protected HashMap documentColors = new HashMap();
716
717 /** The patterns of this document */
718 protected HashMap documentPatterns = new HashMap();
719
720 protected HashMap documentShadings = new HashMap();
721
722 protected HashMap documentShadingPatterns = new HashMap();
723
724 protected ColorDetails patternColorspaceRGB;
725 protected ColorDetails patternColorspaceGRAY;
726 protected ColorDetails patternColorspaceCMYK;
727 protected HashMap documentSpotPatterns = new HashMap();
728
729 protected HashMap documentExtGState = new HashMap();
730
731 protected HashMap documentLayers = new HashMap();
732 protected HashSet documentOCG = new HashSet();
733 protected ArrayList documentOCGorder = new ArrayList();
734 protected PdfOCProperties OCProperties;
735 protected PdfArray OCGRadioGroup = new PdfArray();
736
737 protected PdfDictionary defaultColorspace = new PdfDictionary();
738
739 /** PDF/X value */
740 public static final int PDFXNONE = 0;
741 /** PDF/X value */
742 public static final int PDFX1A2001 = 1;
743 /** PDF/X value */
744 public static final int PDFX32002 = 2;
745
746 private int pdfxConformance = PDFXNONE;
747
748 static final int PDFXKEY_COLOR = 1;
749 static final int PDFXKEY_CMYK = 2;
750 static final int PDFXKEY_RGB = 3;
751 static final int PDFXKEY_FONT = 4;
752 static final int PDFXKEY_IMAGE = 5;
753 static final int PDFXKEY_GSTATE = 6;
754 static final int PDFXKEY_LAYER = 7;
755
756 // membervariables
757
758 /** body of the PDF document */
759 protected PdfBody body;
760
761 /** the pdfdocument object. */
762 protected PdfDocument pdf;
763
764 /** The <CODE>PdfPageEvent</CODE> for this document. */
765 private PdfPageEvent pageEvent;
766
767 protected PdfEncryption crypto;
768
769 protected HashMap importedPages = new HashMap();
770
771 protected PdfReaderInstance currentPdfReaderInstance;
772
773 /** The PdfIndirectReference to the pages. */
774 protected ArrayList pageReferences = new ArrayList();
775
776 protected int currentPageNumber = 1;
777
778 protected PdfDictionary group;
779
780 /** The default space-char ratio. */
781 public static final float SPACE_CHAR_RATIO_DEFAULT = 2.5f;
782 /** Disable the inter-character spacing. */
783 public static final float NO_SPACE_CHAR_RATIO = 10000000f;
784
785 /** Use the default run direction. */
786 public static final int RUN_DIRECTION_DEFAULT = 0;
787 /** Do not use bidirectional reordering. */
788 public static final int RUN_DIRECTION_NO_BIDI = 1;
789 /** Use bidirectional reordering with left-to-right
790 * preferential run direction.
791 */
792 public static final int RUN_DIRECTION_LTR = 2;
793 /** Use bidirectional reordering with right-to-left
794 * preferential run direction.
795 */
796 public static final int RUN_DIRECTION_RTL = 3;
797 protected int runDirection = RUN_DIRECTION_NO_BIDI;
798 /**
799 * The ratio between the extra word spacing and the extra character spacing.
800 * Extra word spacing will grow <CODE>ratio</CODE> times more than extra character spacing.
801 */
802 private float spaceCharRatio = SPACE_CHAR_RATIO_DEFAULT;
803
804 /** Holds value of property extraCatalog. */
805 private PdfDictionary extraCatalog;
806
807 /**
808 * Holds value of property fullCompression.
809 */
810 protected boolean fullCompression = false;
811
812 // constructor
813
814 protected PdfWriter() {
815 }
816
817 /**
818 * Constructs a <CODE>PdfWriter</CODE>.
819 * <P>
820 * Remark: a PdfWriter can only be constructed by calling the method
821 * <CODE>getInstance(Document document, OutputStream os)</CODE>.
822 *
823 * @param document The <CODE>PdfDocument</CODE> that has to be written
824 * @param os The <CODE>OutputStream</CODE> the writer has to write to.
825 */
826
827 protected PdfWriter(PdfDocument document, OutputStream os) {
828 super(document, os);
829 pdf = document;
830 directContent = new PdfContentByte(this);
831 directContentUnder = new PdfContentByte(this);
832 }
833
834 // get an instance of the PdfWriter
835
836 /**
837 * Gets an instance of the <CODE>PdfWriter</CODE>.
838 *
839 * @param document The <CODE>Document</CODE> that has to be written
840 * @param os The <CODE>OutputStream</CODE> the writer has to write to.
841 * @return a new <CODE>PdfWriter</CODE>
842 *
843 * @throws DocumentException on error
844 */
845
846 public static PdfWriter getInstance(Document document, OutputStream os)
847 throws DocumentException {
848 PdfDocument pdf = new PdfDocument();
849 document.addDocListener(pdf);
850 PdfWriter writer = new PdfWriter(pdf, os);
851 pdf.addWriter(writer);
852 return writer;
853 }
854
855 /** Gets an instance of the <CODE>PdfWriter</CODE>.
856 *
857 * @return a new <CODE>PdfWriter</CODE>
858 * @param document The <CODE>Document</CODE> that has to be written
859 * @param os The <CODE>OutputStream</CODE> the writer has to write to.
860 * @param listener A <CODE>DocListener</CODE> to pass to the PdfDocument.
861 * @throws DocumentException on error
862 */
863
864 public static PdfWriter getInstance(Document document, OutputStream os, DocListener listener)
865 throws DocumentException {
866 PdfDocument pdf = new PdfDocument();
867 pdf.addDocListener(listener);
868 document.addDocListener(pdf);
869 PdfWriter writer = new PdfWriter(pdf, os);
870 pdf.addWriter(writer);
871 return writer;
872 }
873
874 // methods to write objects to the outputstream
875
876 /**
877 * Adds some <CODE>PdfContents</CODE> to this Writer.
878 * <P>
879 * The document has to be open before you can begin to add content
880 * to the body of the document.
881 *
882 * @return a <CODE>PdfIndirectReference</CODE>
883 * @param page the <CODE>PdfPage</CODE> to add
884 * @param contents the <CODE>PdfContents</CODE> of the page
885 * @throws PdfException on error
886 */
887
888 PdfIndirectReference add(PdfPage page, PdfContents contents) throws PdfException {
889 if (!open) {
890 throw new PdfException("The document isn't open.");
891 }
892 PdfIndirectObject object;
893 try {
894 object = addToBody(contents);
895 }
896 catch(IOException ioe) {
897 throw new ExceptionConverter(ioe);
898 }
899 page.add(object.getIndirectReference());
900 if (group != null) {
901 page.put(PdfName.GROUP, group);
902 group = null;
903 }
904 root.addPage(page);
905 currentPageNumber++;
906 return null;
907 }
908
909 /** Adds an image to the document but not to the page resources. It is used with
910 * templates and <CODE>Document.add(Image)</CODE>.
911 * @param image the <CODE>Image</CODE> to add
912 * @return the name of the image added
913 * @throws PdfException on error
914 * @throws DocumentException on error
915 */
916 PdfName addDirectImageSimple(Image image) throws PdfException, DocumentException {
917 PdfName name;
918 // if the images is already added, just retrieve the name
919 if (images.containsKey(image.getMySerialId())) {
920 name = (PdfName) images.get(image.getMySerialId());
921 }
922 // if it's a new image, add it to the document
923 else {
924 if (image.isImgTemplate()) {
925 name = new PdfName("img" + images.size());
926 if (image.templateData() == null) {
927 if(image instanceof ImgWMF){
928 try {
929 ImgWMF wmf = (ImgWMF)image;
930 wmf.readWMF(getDirectContent().createTemplate(0, 0));
931 }
932 catch (Exception e) {
933 throw new DocumentException(e);
934 }
935 }else{
936 try {
937 ((ImgPostscript)image).readPostscript(getDirectContent().createTemplate(0, 0));
938 }
939 catch (Exception e) {
940 throw new DocumentException(e);
941 }
942
943 }
944 }
945 }
946 else {
947 Image maskImage = image.getImageMask();
948 PdfIndirectReference maskRef = null;
949 if (maskImage != null) {
950 PdfName mname = (PdfName)images.get(maskImage.getMySerialId());
951 maskRef = getImageReference(mname);
952 }
953 PdfImage i = new PdfImage(image, "img" + images.size(), maskRef);
954 if (image.hasICCProfile()) {
955 PdfICCBased icc = new PdfICCBased(image.getICCProfile());
956 PdfIndirectReference iccRef = add(icc);
957 PdfArray iccArray = new PdfArray();
958 iccArray.add(PdfName.ICCBASED);
959 iccArray.add(iccRef);
960 PdfObject colorspace = i.get(PdfName.COLORSPACE);
961 if (colorspace != null && colorspace.isArray()) {
962 ArrayList ar = ((PdfArray)colorspace).getArrayList();
963 if (ar.size() > 1 && PdfName.INDEXED.equals(ar.get(0)))
964 ar.set(1, iccArray);
965 else
966 i.put(PdfName.COLORSPACE, iccArray);
967 }
968 else
969 i.put(PdfName.COLORSPACE, iccArray);
970 }
971 add(i);
972 name = i.name();
973 }
974 images.put(image.getMySerialId(), name);
975 }
976 return name;
977 }
978
979 /**
980 * Writes a <CODE>PdfImage</CODE> to the outputstream.
981 *
982 * @param pdfImage the image to be added
983 * @return a <CODE>PdfIndirectReference</CODE> to the encapsulated image
984 * @throws PdfException when a document isn't open yet, or has been closed
985 */
986
987 PdfIndirectReference add(PdfImage pdfImage) throws PdfException {
988 if (! imageDictionary.contains(pdfImage.name())) {
989 checkPDFXConformance(this, PDFXKEY_IMAGE, pdfImage);
990 PdfIndirectObject object;
991 try {
992 object = addToBody(pdfImage);
993 }
994 catch(IOException ioe) {
995 throw new ExceptionConverter(ioe);
996 }
997 imageDictionary.put(pdfImage.name(), object.getIndirectReference());
998 return object.getIndirectReference();
999 }
1000 return (PdfIndirectReference) imageDictionary.get(pdfImage.name());
1001 }
1002
1003 protected PdfIndirectReference add(PdfICCBased icc) throws PdfException {
1004 PdfIndirectObject object;
1005 try {
1006 object = addToBody(icc);
1007 }
1008 catch(IOException ioe) {
1009 throw new ExceptionConverter(ioe);
1010 }
1011 return object.getIndirectReference();
1012 }
1013
1014 /**
1015 * return the <CODE>PdfIndirectReference</CODE> to the image with a given name.
1016 *
1017 * @param name the name of the image
1018 * @return a <CODE>PdfIndirectReference</CODE>
1019 */
1020
1021 PdfIndirectReference getImageReference(PdfName name) {
1022 return (PdfIndirectReference) imageDictionary.get(name);
1023 }
1024
1025 // methods to open and close the writer
1026
1027 /**
1028 * Signals that the <CODE>Document</CODE> has been opened and that
1029 * <CODE>Elements</CODE> can be added.
1030 * <P>
1031 * When this method is called, the PDF-document header is
1032 * written to the outputstream.
1033 */
1034
1035 public void open() {
1036 super.open();
1037 try {
1038 os.write(HEADER);
1039 body = new PdfBody(this);
1040 if (pdfxConformance == PDFX32002) {
1041 PdfDictionary sec = new PdfDictionary();
1042 sec.put(PdfName.GAMMA, new PdfArray(new float[]{2.2f,2.2f,2.2f}));
1043 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}));
1044 sec.put(PdfName.WHITEPOINT, new PdfArray(new float[]{0.9505f,1f,1.089f}));
1045 PdfArray arr = new PdfArray(PdfName.CALRGB);
1046 arr.add(sec);
1047 setDefaultColorspace(PdfName.DEFAULTRGB, addToBody(arr).getIndirectReference());
1048 }
1049 }
1050 catch(IOException ioe) {
1051 throw new ExceptionConverter(ioe);
1052 }
1053 }
1054
1055 private static void getOCGOrder(PdfArray order, PdfLayer layer) {
1056 if (!layer.isOnPanel())
1057 return;
1058 if (layer.getTitle() == null)
1059 order.add(layer.getRef());
1060 ArrayList children = layer.getChildren();
1061 if (children == null)
1062 return;
1063 PdfArray kids = new PdfArray();
1064 if (layer.getTitle() != null)
1065 kids.add(new PdfString(layer.getTitle(), PdfObject.TEXT_UNICODE));
1066 for (int k = 0; k < children.size(); ++k) {
1067 getOCGOrder(kids, (PdfLayer)children.get(k));
1068 }
1069 if (kids.size() > 0)
1070 order.add(kids);
1071 }
1072
1073 private void addASEvent(PdfName event, PdfName category) {
1074 PdfArray arr = new PdfArray();
1075 for (Iterator it = documentOCG.iterator(); it.hasNext();) {
1076 PdfLayer layer = (PdfLayer)it.next();
1077 PdfDictionary usage = (PdfDictionary)layer.get(PdfName.USAGE);
1078 if (usage != null && usage.get(category) != null)
1079 arr.add(layer.getRef());
1080 }
1081 if (arr.size() == 0)
1082 return;
1083 PdfDictionary d = (PdfDictionary)OCProperties.get(PdfName.D);
1084 PdfArray arras = (PdfArray)d.get(PdfName.AS);
1085 if (arras == null) {
1086 arras = new PdfArray();
1087 d.put(PdfName.AS, arras);
1088 }
1089 PdfDictionary as = new PdfDictionary();
1090 as.put(PdfName.EVENT, event);
1091 as.put(PdfName.CATEGORY, new PdfArray(category));
1092 as.put(PdfName.OCGS, arr);
1093 arras.add(as);
1094 }
1095
1096 private void fillOCProperties(boolean erase) {
1097 if (OCProperties == null)
1098 OCProperties = new PdfOCProperties();
1099 if (erase) {
1100 OCProperties.remove(PdfName.OCGS);
1101 OCProperties.remove(PdfName.D);
1102 }
1103 if (OCProperties.get(PdfName.OCGS) == null) {
1104 PdfArray gr = new PdfArray();
1105 for (Iterator it = documentOCG.iterator(); it.hasNext();) {
1106 PdfLayer layer = (PdfLayer)it.next();
1107 gr.add(layer.getRef());
1108 }
1109 OCProperties.put(PdfName.OCGS, gr);
1110 }
1111 if (OCProperties.get(PdfName.D) != null)
1112 return;
1113 ArrayList docOrder = new ArrayList(documentOCGorder);
1114 for (Iterator it = docOrder.iterator(); it.hasNext();) {
1115 PdfLayer layer = (PdfLayer)it.next();
1116 if (layer.getParent() != null)
1117 it.remove();
1118 }
1119 PdfArray order = new PdfArray();
1120 for (Iterator it = docOrder.iterator(); it.hasNext();) {
1121 PdfLayer layer = (PdfLayer)it.next();
1122 getOCGOrder(order, layer);
1123 }
1124 PdfDictionary d = new PdfDictionary();
1125 OCProperties.put(PdfName.D, d);
1126 d.put(PdfName.ORDER, order);
1127 PdfArray gr = new PdfArray();
1128 for (Iterator it = documentOCG.iterator(); it.hasNext();) {
1129 PdfLayer layer = (PdfLayer)it.next();
1130 if (!layer.isOn())
1131 gr.add(layer.getRef());
1132 }
1133 if (gr.size() > 0)
1134 d.put(PdfName.OFF, gr);
1135 if (OCGRadioGroup.size() > 0)
1136 d.put(PdfName.RBGROUPS, OCGRadioGroup);
1137 addASEvent(PdfName.VIEW, PdfName.ZOOM);
1138 addASEvent(PdfName.VIEW, PdfName.VIEW);
1139 addASEvent(PdfName.PRINT, PdfName.PRINT);
1140 addASEvent(PdfName.EXPORT, PdfName.EXPORT);
1141 d.put(PdfName.LISTMODE, PdfName.VISIBLEPAGES);
1142 }
1143
1144 protected PdfDictionary getCatalog(PdfIndirectReference rootObj)
1145 {
1146 PdfDictionary catalog = ((PdfDocument)document).getCatalog(rootObj);
1147 if (documentOCG.size() == 0)
1148 return catalog;
1149 fillOCProperties(false);
1150 catalog.put(PdfName.OCPROPERTIES, OCProperties);
1151 return catalog;
1152 }
1153
1154 protected void addSharedObjectsToBody() throws IOException {
1155 // add the fonts
1156 for (Iterator it = documentFonts.values().iterator(); it.hasNext();) {
1157 FontDetails details = (FontDetails)it.next();
1158 details.writeFont(this);
1159 }
1160 // add the form XObjects
1161 for (Iterator it = formXObjects.values().iterator(); it.hasNext();) {
1162 Object objs[] = (Object[])it.next();
1163 PdfTemplate template = (PdfTemplate)objs[1];
1164 if (template != null && template.getIndirectReference() instanceof PRIndirectReference)
1165 continue;
1166 if (template != null && template.getType() == PdfTemplate.TYPE_TEMPLATE) {
1167 PdfIndirectObject obj = addToBody(template.getFormXObject(), template.getIndirectReference());
1168 }
1169 }
1170 // add all the dependencies in the imported pages
1171 for (Iterator it = importedPages.values().iterator(); it.hasNext();) {
1172 currentPdfReaderInstance = (PdfReaderInstance)it.next();
1173 currentPdfReaderInstance.writeAllPages();
1174 }
1175 currentPdfReaderInstance = null;
1176 // add the color
1177 for (Iterator it = documentColors.values().iterator(); it.hasNext();) {
1178 ColorDetails color = (ColorDetails)it.next();
1179 PdfIndirectObject cobj = addToBody(color.getSpotColor(this), color.getIndirectReference());
1180 }
1181 // add the pattern
1182 for (Iterator it = documentPatterns.keySet().iterator(); it.hasNext();) {
1183 PdfPatternPainter pat = (PdfPatternPainter)it.next();
1184 PdfIndirectObject pobj = addToBody(pat.getPattern(), pat.getIndirectReference());
1185 }
1186 // add the shading patterns
1187 for (Iterator it = documentShadingPatterns.keySet().iterator(); it.hasNext();) {
1188 PdfShadingPattern shadingPattern = (PdfShadingPattern)it.next();
1189 shadingPattern.addToBody();
1190 }
1191 // add the shadings
1192 for (Iterator it = documentShadings.keySet().iterator(); it.hasNext();) {
1193 PdfShading shading = (PdfShading)it.next();
1194 shading.addToBody();
1195 }
1196 // add the extgstate
1197 for (Iterator it = documentExtGState.keySet().iterator(); it.hasNext();) {
1198 PdfDictionary gstate = (PdfDictionary)it.next();
1199 PdfObject obj[] = (PdfObject[])documentExtGState.get(gstate);
1200 addToBody(gstate, (PdfIndirectReference)obj[1]);
1201 }
1202 // add the layers
1203 for (Iterator it = documentLayers.keySet().iterator(); it.hasNext();) {
1204 PdfOCG layer = (PdfOCG)it.next();
1205 if (layer instanceof PdfLayerMembership)
1206 addToBody(layer.getPdfObject(), layer.getRef());
1207 }
1208 for (Iterator it = documentOCG.iterator(); it.hasNext();) {
1209 PdfOCG layer = (PdfOCG)it.next();
1210 addToBody(layer.getPdfObject(), layer.getRef());
1211 }
1212 }
1213
1214 /**
1215 * Signals that the <CODE>Document</CODE> was closed and that no other
1216 * <CODE>Elements</CODE> will be added.
1217 * <P>
1218 * The pages-tree is built and written to the outputstream.
1219 * A Catalog is constructed, as well as an Info-object,
1220 * the referencetable is composed and everything is written
1221 * to the outputstream embedded in a Trailer.
1222 */
1223
1224 public synchronized void close() {
1225 if (open) {
1226 if ((currentPageNumber - 1) != pageReferences.size())
1227 throw new RuntimeException("The page " + pageReferences.size() +
1228 " was requested but the document has only " + (currentPageNumber - 1) + " pages.");
1229 pdf.close();
1230 try {
1231 addSharedObjectsToBody();
1232 // add the root to the body
1233 PdfIndirectReference rootRef = root.writePageTree();
1234 // make the catalog-object and add it to the body
1235 PdfDictionary catalog = getCatalog(rootRef);
1236 // make pdfx conformant
1237 PdfDictionary info = getInfo();
1238 if (pdfxConformance != PDFXNONE) {
1239 if (info.get(PdfName.GTS_PDFXVERSION) == null) {
1240 if (pdfxConformance == PDFX1A2001) {
1241 info.put(PdfName.GTS_PDFXVERSION, new PdfString("PDF/X-1:2001"));
1242 info.put(new PdfName("GTS_PDFXConformance"), new PdfString("PDF/X-1a:2001"));
1243 }
1244 else if (pdfxConformance == PDFX32002)
1245 info.put(PdfName.GTS_PDFXVERSION, new PdfString("PDF/X-3:2002"));
1246 }
1247 if (info.get(PdfName.TITLE) == null) {
1248 info.put(PdfName.TITLE, new PdfString("Pdf document"));
1249 }
1250 if (info.get(PdfName.CREATOR) == null) {
1251 info.put(PdfName.CREATOR, new PdfString("Unknown"));
1252 }
1253 if (info.get(PdfName.TRAPPED) == null) {
1254 info.put(PdfName.TRAPPED, new PdfName("False"));
1255 }
1256 getExtraCatalog();
1257 if (extraCatalog.get(PdfName.OUTPUTINTENTS) == null) {
1258 PdfDictionary out = new PdfDictionary(PdfName.OUTPUTINTENT);
1259 out.put(PdfName.OUTPUTCONDITION, new PdfString("SWOP CGATS TR 001-1995"));
1260 out.put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString("CGATS TR 001"));
1261 out.put(PdfName.REGISTRYNAME, new PdfString("http://www.color.org"));
1262 out.put(PdfName.INFO, new PdfString(""));
1263 out.put(PdfName.S, PdfName.GTS_PDFX);
1264 extraCatalog.put(PdfName.OUTPUTINTENTS, new PdfArray(out));
1265 }
1266 }
1267 if (extraCatalog != null) {
1268 catalog.mergeDifferent(extraCatalog);
1269 }
1270 PdfIndirectObject indirectCatalog = addToBody(catalog, false);
1271 // add the info-object to the body
1272 PdfIndirectObject infoObj = addToBody(info, false);
1273 PdfIndirectReference encryption = null;
1274 PdfObject fileID = null;
1275 body.flushObjStm();
1276 if (crypto != null) {
1277 PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false);
1278 encryption = encryptionObject.getIndirectReference();
1279 fileID = crypto.getFileID();
1280 }
1281 else
1282 fileID = PdfEncryption.createInfoId(PdfEncryption.createDocumentId());
1283
1284 // write the cross-reference table of the body
1285 body.writeCrossReferenceTable(os, indirectCatalog.getIndirectReference(),
1286 infoObj.getIndirectReference(), encryption, fileID, prevxref);
1287
1288 // make the trailer
1289 if (fullCompression) {
1290 os.write(getISOBytes("startxref\n"));
1291 os.write(getISOBytes(String.valueOf(body.offset())));
1292 os.write(getISOBytes("\n%%EOF\n"));
1293 }
1294 else {
1295 PdfTrailer trailer = new PdfTrailer(body.size(),
1296 body.offset(),
1297 indirectCatalog.getIndirectReference(),
1298 infoObj.getIndirectReference(),
1299 encryption,
1300 fileID, prevxref);
1301 trailer.toPdf(this, os);
1302 }
1303 super.close();
1304 }
1305 catch(IOException ioe) {
1306 throw new ExceptionConverter(ioe);
1307 }
1308 }
1309 }
1310
1311 // methods
1312
1313 /**
1314 * Sometimes it is necessary to know where the just added <CODE>Table</CODE> ends.
1315 *
1316 * For instance to avoid to add another table in a page that is ending up, because
1317 * the new table will be probably splitted just after the header (it is an
1318 * unpleasant effect, isn't it?).
1319 *
1320 * Added on September 8th, 2001
1321 * by Francesco De Milato
1322 * francesco.demilato@tiscalinet.it
1323 * @param table the <CODE>Table</CODE>
1324 * @return the bottom height of the just added table
1325 */
1326
1327 public float getTableBottom(Table table) {
1328 return pdf.bottom(table) - pdf.indentBottom();
1329 }
1330
1331 /**
1332 * Gets a pre-rendered table.
1333 * (Contributed by dperezcar@fcc.es)
1334 * @param table Contains the table definition. Its contents are deleted, after being pre-rendered.
1335 * @return a PdfTable
1336 */
1337
1338 public PdfTable getPdfTable(Table table) {
1339 return pdf.getPdfTable(table, true);
1340 }
1341
1342 /**
1343 * Row additions to the original {@link Table} used to build the {@link PdfTable} are processed and pre-rendered,
1344 * and then the contents are deleted.
1345 * If the pre-rendered table doesn't fit, then it is fully rendered and its data discarded.
1346 * There shouldn't be any column change in the underlying {@link Table} object.
1347 * (Contributed by dperezcar@fcc.es)
1348 *
1349 * @param table The pre-rendered table obtained from {@link #getPdfTable(Table)}
1350 * @return true if the table is rendered and emptied.
1351 * @throws DocumentException
1352 * @see #getPdfTable(Table)
1353 */
1354
1355 public boolean breakTableIfDoesntFit(PdfTable table) throws DocumentException {
1356 return pdf.breakTableIfDoesntFit(table);
1357 }
1358
1359 /**
1360 * Checks if a <CODE>Table</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
1361 *
1362 * @param table the table that has to be checked
1363 * @param margin a certain margin
1364 * @return <CODE>true</CODE> if the <CODE>Table</CODE> fits the page, <CODE>false</CODE> otherwise.
1365 */
1366
1367 public boolean fitsPage(Table table, float margin) {
1368 return pdf.bottom(table) > pdf.indentBottom() + margin;
1369 }
1370
1371 /**
1372 * Checks if a <CODE>Table</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
1373 *
1374 * @param table the table that has to be checked
1375 * @return <CODE>true</CODE> if the <CODE>Table</CODE> fits the page, <CODE>false</CODE> otherwise.
1376 */
1377
1378 public boolean fitsPage(Table table) {
1379 return fitsPage(table, 0);
1380 }
1381
1382 /**
1383 * Checks if a <CODE>PdfPTable</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
1384 *
1385 * @param table the table that has to be checked
1386 * @param margin a certain margin
1387 * @return <CODE>true</CODE> if the <CODE>PdfPTable</CODE> fits the page, <CODE>false</CODE> otherwise.
1388 */
1389 public boolean fitsPage(PdfPTable table, float margin) {
1390 return pdf.fitsPage(table, margin);
1391 }
1392
1393 /**
1394 * Checks if a <CODE>PdfPTable</CODE> fits the current page of the <CODE>PdfDocument</CODE>.
1395 *
1396 * @param table the table that has to be checked
1397 * @return <CODE>true</CODE> if the <CODE>PdfPTable</CODE> fits the page, <CODE>false</CODE> otherwise.
1398 */
1399 public boolean fitsPage(PdfPTable table) {
1400 return pdf.fitsPage(table, 0);
1401 }
1402
1403 /**
1404 * Gets the current vertical page position.
1405 * @param ensureNewLine Tells whether a new line shall be enforced. This may cause side effects
1406 * for elements that do not terminate the lines they've started because those lines will get
1407 * terminated.
1408 * @return The current vertical page position.
1409 */
1410 public float getVerticalPosition(boolean ensureNewLine) {
1411 return pdf.getVerticalPosition(ensureNewLine);
1412 }
1413
1414 /**
1415 * Checks if writing is paused.
1416 *
1417 * @return <CODE>true</CODE> if writing temporarely has to be paused, <CODE>false</CODE> otherwise.
1418 */
1419
1420 boolean isPaused() {
1421 return pause;
1422 }
1423
1424 /**
1425 * Gets the direct content for this document. There is only one direct content,
1426 * multiple calls to this method will allways retrieve the same.
1427 * @return the direct content
1428 */
1429
1430 public PdfContentByte getDirectContent() {
1431 if (!open)
1432 throw new RuntimeException("The document is not open.");
1433 return directContent;
1434 }
1435
1436 /**
1437 * Gets the direct content under for this document. There is only one direct content,
1438 * multiple calls to this method will allways retrieve the same.
1439 * @return the direct content
1440 */
1441
1442 public PdfContentByte getDirectContentUnder() {
1443 if (!open)
1444 throw new RuntimeException("The document is not open.");
1445 return directContentUnder;
1446 }
1447
1448 /**
1449 * Resets all the direct contents to empty. This happens when a new page is started.
1450 */
1451
1452 void resetContent() {
1453 directContent.reset();
1454 directContentUnder.reset();
1455 }
1456
1457 /** Gets the AcroForm object.
1458 * @return the <CODE>PdfAcroForm</CODE>
1459 */
1460
1461 public PdfAcroForm getAcroForm() {
1462 return pdf.getAcroForm();
1463 }
1464
1465 /** Gets the root outline.
1466 * @return the root outline
1467 */
1468
1469 public PdfOutline getRootOutline() {
1470 return directContent.getRootOutline();
1471 }
1472
1473 /**
1474 * Returns the outputStreamCounter.
1475 * @return the outputStreamCounter
1476 */
1477 public OutputStreamCounter getOs() {
1478 return os;
1479 }
1480
1481 /**
1482 * Adds a <CODE>BaseFont</CODE> to the document but not to the page resources.
1483 * It is used for templates.
1484 * @param bf the <CODE>BaseFont</CODE> to add
1485 * @return an <CODE>Object[]</CODE> where position 0 is a <CODE>PdfName</CODE>
1486 * and position 1 is an <CODE>PdfIndirectReference</CODE>
1487 */
1488
1489 FontDetails addSimple(BaseFont bf) {
1490 if (bf.getFontType() == BaseFont.FONT_TYPE_DOCUMENT) {
1491 return new FontDetails(new PdfName("F" + (fontNumber++)), ((DocumentFont)bf).getIndirectReference(), bf);
1492 }
1493 FontDetails ret = (FontDetails)documentFonts.get(bf);
1494 if (ret == null) {
1495 checkPDFXConformance(this, PDFXKEY_FONT, bf);
1496 ret = new FontDetails(new PdfName("F" + (fontNumber++)), body.getPdfIndirectReference(), bf);
1497 documentFonts.put(bf, ret);
1498 }
1499 return ret;
1500 }
1501
1502 void eliminateFontSubset(PdfDictionary fonts) {
1503 for (Iterator it = documentFonts.values().iterator(); it.hasNext();) {
1504 FontDetails ft = (FontDetails)it.next();
1505 if (fonts.get(ft.getFontName()) != null)
1506 ft.setSubset(false);
1507 }
1508 }
1509
1510 /**
1511 * Adds a <CODE>SpotColor</CODE> to the document but not to the page resources.
1512 * @param spc the <CODE>SpotColor</CODE> to add
1513 * @return an <CODE>Object[]</CODE> where position 0 is a <CODE>PdfName</CODE>
1514 * and position 1 is an <CODE>PdfIndirectReference</CODE>
1515 */
1516
1517 ColorDetails addSimple(PdfSpotColor spc) {
1518 ColorDetails ret = (ColorDetails)documentColors.get(spc);
1519 if (ret == null) {
1520 ret = new ColorDetails(new PdfName("CS" + (colorNumber++)), body.getPdfIndirectReference(), spc);
1521 documentColors.put(spc, ret);
1522 }
1523 return ret;
1524 }
1525
1526 ColorDetails addSimplePatternColorspace(Color color) {
1527 int type = ExtendedColor.getType(color);
1528 if (type == ExtendedColor.TYPE_PATTERN || type == ExtendedColor.TYPE_SHADING)
1529 throw new RuntimeException("An uncolored tile pattern can not have another pattern or shading as color.");
1530 try {
1531 switch (type) {
1532 case ExtendedColor.TYPE_RGB:
1533 if (patternColorspaceRGB == null) {
1534 patternColorspaceRGB = new ColorDetails(new PdfName("CS" + (colorNumber++)), body.getPdfIndirectReference(), null);
1535 PdfArray array = new PdfArray(PdfName.PATTERN);
1536 array.add(PdfName.DEVICERGB);
1537 PdfIndirectObject cobj = addToBody(array, patternColorspaceRGB.getIndirectReference());
1538 }
1539 return patternColorspaceRGB;
1540 case ExtendedColor.TYPE_CMYK:
1541 if (patternColorspaceCMYK == null) {
1542 patternColorspaceCMYK = new ColorDetails(new PdfName("CS" + (colorNumber++)), body.getPdfIndirectReference(), null);
1543 PdfArray array = new PdfArray(PdfName.PATTERN);
1544 array.add(PdfName.DEVICECMYK);
1545 PdfIndirectObject cobj = addToBody(array, patternColorspaceCMYK.getIndirectReference());
1546 }
1547 return patternColorspaceCMYK;
1548 case ExtendedColor.TYPE_GRAY:
1549 if (patternColorspaceGRAY == null) {
1550 patternColorspaceGRAY = new ColorDetails(new PdfName("CS" + (colorNumber++)), body.getPdfIndirectReference(), null);
1551 PdfArray array = new PdfArray(PdfName.PATTERN);
1552 array.add(PdfName.DEVICEGRAY);
1553 PdfIndirectObject cobj = addToBody(array, patternColorspaceGRAY.getIndirectReference());
1554 }
1555 return patternColorspaceGRAY;
1556 case ExtendedColor.TYPE_SEPARATION: {
1557 ColorDetails details = addSimple(((SpotColor)color).getPdfSpotColor());
1558 ColorDetails patternDetails = (ColorDetails)documentSpotPatterns.get(details);
1559 if (patternDetails == null) {
1560 patternDetails = new ColorDetails(new PdfName("CS" + (colorNumber++)), body.getPdfIndirectReference(), null);
1561 PdfArray array = new PdfArray(PdfName.PATTERN);
1562 array.add(details.getIndirectReference());
1563 PdfIndirectObject cobj = addToBody(array, patternDetails.getIndirectReference());
1564 documentSpotPatterns.put(details, patternDetails);
1565 }
1566 return patternDetails;
1567 }
1568 default:
1569 throw new RuntimeException("Invalid color type in PdfWriter.addSimplePatternColorspace().");
1570 }
1571 }
1572 catch (Exception e) {
1573 throw new RuntimeException(e.getMessage());
1574 }
1575 }
1576
1577 void addSimpleShadingPattern(PdfShadingPattern shading) {
1578 if (!documentShadingPatterns.containsKey(shading)) {
1579 shading.setName(patternNumber);
1580 ++patternNumber;
1581 documentShadingPatterns.put(shading, null);
1582 addSimpleShading(shading.getShading());
1583 }
1584 }
1585
1586 void addSimpleShading(PdfShading shading) {
1587 if (!documentShadings.containsKey(shading)) {
1588 documentShadings.put(shading, null);
1589 shading.setName(documentShadings.size());
1590 }
1591 }
1592
1593 PdfObject[] addSimpleExtGState(PdfDictionary gstate) {
1594 if (!documentExtGState.containsKey(gstate)) {
1595 checkPDFXConformance(this, PDFXKEY_GSTATE, gstate);
1596 documentExtGState.put(gstate, new PdfObject[]{new PdfName("GS" + (documentExtGState.size() + 1)), getPdfIndirectReference()});
1597 }
1598 return (PdfObject[])documentExtGState.get(gstate);
1599 }
1600
1601 void registerLayer(PdfOCG layer) {
1602 checkPDFXConformance(this, PDFXKEY_LAYER, null);
1603 if (layer instanceof PdfLayer) {
1604 PdfLayer la = (PdfLayer)layer;
1605 if (la.getTitle() == null) {
1606 if (!documentOCG.contains(layer)) {
1607 documentOCG.add(layer);
1608 documentOCGorder.add(layer);
1609 }
1610 }
1611 else {
1612 documentOCGorder.add(layer);
1613 }
1614 }
1615 else
1616 throw new IllegalArgumentException("Only PdfLayer is accepted.");
1617 }
1618
1619 PdfName addSimpleLayer(PdfOCG layer) {
1620 if (!documentLayers.containsKey(layer)) {
1621 checkPDFXConformance(this, PDFXKEY_LAYER, null);
1622 documentLayers.put(layer, new PdfName("OC" + (documentLayers.size() + 1)));
1623 }
1624 return (PdfName)documentLayers.get(layer);
1625 }
1626
1627 /**
1628 * Gets the <CODE>PdfDocument</CODE> associated with this writer.
1629 * @return the <CODE>PdfDocument</CODE>
1630 */
1631
1632 PdfDocument getPdfDocument() {
1633 return pdf;
1634 }
1635
1636 /**
1637 * Gets a <CODE>PdfIndirectReference</CODE> for an object that
1638 * will be created in the future.
1639 * @return the <CODE>PdfIndirectReference</CODE>
1640 */
1641
1642 public PdfIndirectReference getPdfIndirectReference() {
1643 return body.getPdfIndirectReference();
1644 }
1645
1646 int getIndirectReferenceNumber() {
1647 return body.getIndirectReferenceNumber();
1648 }
1649
1650 PdfName addSimplePattern(PdfPatternPainter painter) {
1651 PdfName name = (PdfName)documentPatterns.get(painter);
1652 try {
1653 if ( name == null ) {
1654 name = new PdfName("P" + patternNumber);
1655 ++patternNumber;
1656 documentPatterns.put(painter, name);
1657 }
1658 } catch (Exception e) {
1659 throw new ExceptionConverter(e);
1660 }
1661 return name;
1662 }
1663
1664 /**
1665 * Adds a template to the document but not to the page resources.
1666 * @param template the template to add
1667 * @param forcedName the template name, rather than a generated one. Can be null
1668 * @return the <CODE>PdfName</CODE> for this template
1669 */
1670
1671 PdfName addDirectTemplateSimple(PdfTemplate template, PdfName forcedName) {
1672 PdfIndirectReference ref = template.getIndirectReference();
1673 Object obj[] = (Object[])formXObjects.get(ref);
1674 PdfName name = null;
1675 try {
1676 if (obj == null) {
1677 if (forcedName == null) {
1678 name = new PdfName("Xf" + formXObjectsCounter);
1679 ++formXObjectsCounter;
1680 }
1681 else
1682 name = forcedName;
1683 if (template.getType() == PdfTemplate.TYPE_IMPORTED)
1684 template = null;
1685 formXObjects.put(ref, new Object[]{name, template});
1686 }
1687 else
1688 name = (PdfName)obj[0];
1689 }
1690 catch (Exception e) {
1691 throw new ExceptionConverter(e);
1692 }
1693 return name;
1694 }
1695
1696 /**
1697 * Sets the <CODE>PdfPageEvent</CODE> for this document.
1698 * @param pageEvent the <CODE>PdfPageEvent</CODE> for this document
1699 */
1700
1701 public void setPageEvent(PdfPageEvent pageEvent) {
1702 this.pageEvent = pageEvent;
1703 }
1704
1705 /**
1706 * Gets the <CODE>PdfPageEvent</CODE> for this document or <CODE>null</CODE>
1707 * if none is set.
1708 * @return the <CODE>PdfPageEvent</CODE> for this document or <CODE>null</CODE>
1709 * if none is set
1710 */
1711
1712 public PdfPageEvent getPageEvent() {
1713 return pageEvent;
1714 }
1715
1716 /**
1717 * Adds the local destinations to the body of the document.
1718 * @param dest the <CODE>HashMap</CODE> containing the destinations
1719 * @throws IOException on error
1720 */
1721
1722 void addLocalDestinations(TreeMap dest) throws IOException {
1723 for (Iterator i = dest.keySet().iterator(); i.hasNext();) {
1724 String name = (String)i.next();
1725 Object obj[] = (Object[])dest.get(name);
1726 PdfDestination destination = (PdfDestination)obj[2];
1727 if (destination == null)
1728 throw new RuntimeException("The name '" + name + "' has no local destination.");
1729 if (obj[1] == null)
1730 obj[1] = getPdfIndirectReference();
1731 PdfIndirectObject iob = addToBody(destination, (PdfIndirectReference)obj[1]);
1732 }
1733 }
1734
1735 /**
1736 * Gets the current pagenumber of this document.
1737 *
1738 * @return a page number
1739 */
1740
1741 public int getPageNumber() {
1742 return pdf.getPageNumber();
1743 }
1744
1745 /**
1746 * Sets the viewer preferences by ORing some constants.
1747 * <p>
1748 * <ul>
1749 * <li>The page layout to be used when the document is opened (choose one).
1750 * <ul>
1751 * <li><b>PageLayoutSinglePage</b> - Display one page at a time. (default)
1752 * <li><b>PageLayoutOneColumn</b> - Display the pages in one column.
1753 * <li><b>PageLayoutTwoColumnLeft</b> - Display the pages in two columns, with
1754 * oddnumbered pages on the left.
1755 * <li><b>PageLayoutTwoColumnRight</b> - Display the pages in two columns, with
1756 * oddnumbered pages on the right.
1757 * </ul>
1758 * <li>The page mode how the document should be displayed
1759 * when opened (choose one).
1760 * <ul>
1761 * <li><b>PageModeUseNone</b> - Neither document outline nor thumbnail images visible. (default)
1762 * <li><b>PageModeUseOutlines</b> - Document outline visible.
1763 * <li><b>PageModeUseThumbs</b> - Thumbnail images visible.
1764 * <li><b>PageModeFullScreen</b> - Full-screen mode, with no menu bar, window
1765 * controls, or any other window visible.
1766 * <li><b>PageModeUseOC</b> - Optional content group panel visible
1767 * </ul>
1768 * <li><b>HideToolbar</b> - A flag specifying whether to hide the viewer application's tool
1769 * bars when the document is active.
1770 * <li><b>HideMenubar</b> - A flag specifying whether to hide the viewer application's
1771 * menu bar when the document is active.
1772 * <li><b>HideWindowUI</b> - A flag specifying whether to hide user interface elements in
1773 * the document's window (such as scroll bars and navigation controls),
1774 * leaving only the document's contents displayed.
1775 * <li><b>FitWindow</b> - A flag specifying whether to resize the document's window to
1776 * fit the size of the first displayed page.
1777 * <li><b>CenterWindow</b> - A flag specifying whether to position the document's window
1778 * in the center of the screen.
1779 * <li><b>DisplayDocTitle</b> - A flag specifying whether to display the document's title
1780 * in the top bar.
1781 * <li>The predominant reading order for text. This entry has no direct effect on the
1782 * document's contents or page numbering, but can be used to determine the relative
1783 * positioning of pages when displayed side by side or printed <i>n-up</i> (choose one).
1784 * <ul>
1785 * <li><b>DirectionL2R</b> - Left to right
1786 * <li><b>DirectionR2L</b> - Right to left (including vertical writing systems such as
1787 * Chinese, Japanese, and Korean)
1788 * </ul>
1789 * <li>The document's page mode, specifying how to display the
1790 * document on exiting full-screen mode. It is meaningful only
1791 * if the page mode is <b>PageModeFullScreen</b> (choose one).
1792 * <ul>
1793 * <li><b>NonFullScreenPageModeUseNone</b> - Neither document outline nor thumbnail images
1794 * visible
1795 * <li><b>NonFullScreenPageModeUseOutlines</b> - Document outline visible
1796 * <li><b>NonFullScreenPageModeUseThumbs</b> - Thumbnail images visible
1797 * <li><b>NonFullScreenPageModeUseOC</b> - Optional content group panel visible
1798 * </ul>
1799 * <li><b>PrintScalingNone</b> - Indicates that the print dialog should reflect no page scaling.
1800 * </ul>
1801 * @param preferences the viewer preferences
1802 */
1803
1804 public void setViewerPreferences(int preferences) {
1805 pdf.setViewerPreferences(preferences);
1806 }
1807
1808 /** Sets the encryption options for this document. The userPassword and the
1809 * ownerPassword can be null or have zero length. In this case the ownerPassword
1810 * is replaced by a random string. The open permissions for the document can be
1811 * AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
1812 * AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
1813 * The permissions can be combined by ORing them.
1814 * @param userPassword the user password. Can be null or empty
1815 * @param ownerPassword the owner password. Can be null or empty
1816 * @param permissions the user permissions
1817 * @param strength128Bits <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length
1818 * @throws DocumentException if the document is already open
1819 */
1820 public void setEncryption(byte userPassword[], byte ownerPassword[], int permissions, boolean strength128Bits) throws DocumentException {
1821 if (pdf.isOpen())
1822 throw new DocumentException("Encryption can only be added before opening the document.");
1823 crypto = new PdfEncryption();
1824 crypto.setupAllKeys(userPassword, ownerPassword, permissions, strength128Bits);
1825 }
1826
1827 /**
1828 * Sets the encryption options for this document. The userPassword and the
1829 * ownerPassword can be null or have zero length. In this case the ownerPassword
1830 * is replaced by a random string. The open permissions for the document can be
1831 * AllowPrinting, AllowModifyContents, AllowCopy, AllowModifyAnnotations,
1832 * AllowFillIn, AllowScreenReaders, AllowAssembly and AllowDegradedPrinting.
1833 * The permissions can be combined by ORing them.
1834 * @param strength <code>true</code> for 128 bit key length, <code>false</code> for 40 bit key length
1835 * @param userPassword the user password. Can be null or empty
1836 * @param ownerPassword the owner password. Can be null or empty
1837 * @param permissions the user permissions
1838 * @throws DocumentException if the document is already open
1839 */
1840 public void setEncryption(boolean strength, String userPassword, String ownerPassword, int permissions) throws DocumentException {
1841 setEncryption(getISOBytes(userPassword), getISOBytes(ownerPassword), permissions, strength);
1842 }
1843
1844 /**
1845 * Adds an object to the PDF body.
1846 * @param object
1847 * @return a PdfIndirectObject
1848 * @throws IOException
1849 */
1850 public PdfIndirectObject addToBody(PdfObject object) throws IOException {
1851 PdfIndirectObject iobj = body.add(object);
1852 return iobj;
1853 }
1854
1855 /**
1856 * Adds an object to the PDF body.
1857 * @param object
1858 * @param inObjStm
1859 * @return a PdfIndirectObject
1860 * @throws IOException
1861 */
1862 public PdfIndirectObject addToBody(PdfObject object, boolean inObjStm) throws IOException {
1863 PdfIndirectObject iobj = body.add(object, inObjStm);
1864 return iobj;
1865 }
1866
1867 /**
1868 * Adds an object to the PDF body.
1869 * @param object
1870 * @param ref
1871 * @return a PdfIndirectObject
1872 * @throws IOException
1873 */
1874 public PdfIndirectObject addToBody(PdfObject object, PdfIndirectReference ref) throws IOException {
1875 PdfIndirectObject iobj = body.add(object, ref);
1876 return iobj;
1877 }
1878
1879 /**
1880 * Adds an object to the PDF body.
1881 * @param object
1882 * @param ref
1883 * @param inObjStm
1884 * @return a PdfIndirectObject
1885 * @throws IOException
1886 */
1887 public PdfIndirectObject addToBody(PdfObject object, PdfIndirectReference ref, boolean inObjStm) throws IOException {
1888 PdfIndirectObject iobj = body.add(object, ref, inObjStm);
1889 return iobj;
1890 }
1891
1892 /**
1893 * Adds an object to the PDF body.
1894 * @param object
1895 * @param refNumber
1896 * @return a PdfIndirectObject
1897 * @throws IOException
1898 */
1899 public PdfIndirectObject addToBody(PdfObject object, int refNumber) throws IOException {
1900 PdfIndirectObject iobj = body.add(object, refNumber);
1901 return iobj;
1902 }
1903
1904 /**
1905 * Adds an object to the PDF body.
1906 * @param object
1907 * @param refNumber
1908 * @param inObjStm
1909 * @return a PdfIndirectObject
1910 * @throws IOException
1911 */
1912 public PdfIndirectObject addToBody(PdfObject object, int refNumber, boolean inObjStm) throws IOException {
1913 PdfIndirectObject iobj = body.add(object, refNumber, inObjStm);
1914 return iobj;
1915 }
1916
1917 /** When the document opens it will jump to the destination with
1918 * this name.
1919 * @param name the name of the destination to jump to
1920 */
1921 public void setOpenAction(String name) {
1922 pdf.setOpenAction(name);
1923 }
1924
1925 /** Additional-actions defining the actions to be taken in
1926 * response to various trigger events affecting the document
1927 * as a whole. The actions types allowed are: <CODE>DOCUMENT_CLOSE</CODE>,
1928 * <CODE>WILL_SAVE</CODE>, <CODE>DID_SAVE</CODE>, <CODE>WILL_PRINT</CODE>
1929 * and <CODE>DID_PRINT</CODE>.
1930 *
1931 * @param actionType the action type
1932 * @param action the action to execute in response to the trigger
1933 * @throws PdfException on invalid action type
1934 */
1935 public void setAdditionalAction(PdfName actionType, PdfAction action) throws PdfException {
1936 if (!(actionType.equals(DOCUMENT_CLOSE) ||
1937 actionType.equals(WILL_SAVE) ||
1938 actionType.equals(DID_SAVE) ||
1939 actionType.equals(WILL_PRINT) ||
1940 actionType.equals(DID_PRINT))) {
1941 throw new PdfException("Invalid additional action type: " + actionType.toString());
1942 }
1943 pdf.addAdditionalAction(actionType, action);
1944 }
1945
1946 /** When the document opens this <CODE>action</CODE> will be
1947 * invoked.
1948 * @param action the action to be invoked
1949 */
1950 public void setOpenAction(PdfAction action) {
1951 pdf.setOpenAction(action);
1952 }
1953
1954 /** Sets the page labels
1955 * @param pageLabels the page labels
1956 */
1957 public void setPageLabels(PdfPageLabels pageLabels) {
1958 pdf.setPageLabels(pageLabels);
1959 }
1960
1961 PdfEncryption getEncryption() {
1962 return crypto;
1963 }
1964
1965 RandomAccessFileOrArray getReaderFile(PdfReader reader) {
1966 return currentPdfReaderInstance.getReaderFile();
1967 }
1968
1969 protected int getNewObjectNumber(PdfReader reader, int number, int generation) {
1970 return currentPdfReaderInstance.getNewObjectNumber(number, generation);
1971 }
1972
1973 /** Gets a page from other PDF document. The page can be used as
1974 * any other PdfTemplate. Note that calling this method more than
1975 * once with the same parameters will retrieve the same object.
1976 * @param reader the PDF document where the page is
1977 * @param pageNumber the page number. The first page is 1
1978 * @return the template representing the imported page
1979 */
1980 public PdfImportedPage getImportedPage(PdfReader reader, int pageNumber) {
1981 PdfReaderInstance inst = (PdfReaderInstance)importedPages.get(reader);
1982 if (inst == null) {
1983 inst = reader.getPdfReaderInstance(this);
1984 importedPages.put(reader, inst);
1985 }
1986 return inst.getImportedPage(pageNumber);
1987 }
1988
1989 /** Adds a JavaScript action at the document level. When the document
1990 * opens all this JavaScript runs.
1991 * @param js The JavaScrip action
1992 */
1993 public void addJavaScript(PdfAction js) {
1994 pdf.addJavaScript(js);
1995 }
1996
1997 /** Adds a JavaScript action at the document level. When the document
1998 * opens all this JavaScript runs.
1999 * @param code the JavaScript code
2000 * @param unicode select JavaScript unicode. Note that the internal
2001 * Acrobat JavaScript engine does not support unicode,
2002 * so this may or may not work for you
2003 */
2004 public void addJavaScript(String code, boolean unicode) {
2005 addJavaScript(PdfAction.javaScript(code, this, unicode));
2006 }
2007
2008 /** Adds a JavaScript action at the document level. When the document
2009 * opens all this JavaScript runs.
2010 * @param code the JavaScript code
2011 */
2012 public void addJavaScript(String code) {
2013 addJavaScript(code, false);
2014 }
2015
2016 /** Sets the crop box. The crop box should not be rotated even if the
2017 * page is rotated. This change only takes effect in the next
2018 * page.
2019 * @param crop the crop box
2020 */
2021 public void setCropBoxSize(Rectangle crop) {
2022 pdf.setCropBoxSize(crop);
2023 }
2024
2025 /** Gets a reference to a page existing or not. If the page does not exist
2026 * yet the reference will be created in advance. If on closing the document, a
2027 * page number greater than the total number of pages was requested, an
2028 * exception is thrown.
2029 * @param page the page number. The first page is 1
2030 * @return the reference to the page
2031 */
2032 public PdfIndirectReference getPageReference(int page) {
2033 --page;
2034 if (page < 0)
2035 throw new IndexOutOfBoundsException("The page numbers start at 1.");
2036 PdfIndirectReference ref;
2037 if (page < pageReferences.size()) {
2038 ref = (PdfIndirectReference)pageReferences.get(page);
2039 if (ref == null) {
2040 ref = body.getPdfIndirectReference();
2041 pageReferences.set(page, ref);
2042 }
2043 }
2044 else {
2045 int empty = page - pageReferences.size();
2046 for (int k = 0; k < empty; ++k)
2047 pageReferences.add(null);
2048 ref = body.getPdfIndirectReference();
2049 pageReferences.add(ref);
2050 }
2051 return ref;
2052 }
2053
2054 PdfIndirectReference getCurrentPage() {
2055 return getPageReference(currentPageNumber);
2056 }
2057
2058 int getCurrentPageNumber() {
2059 return currentPageNumber;
2060 }
2061
2062 /** Adds the <CODE>PdfAnnotation</CODE> to the calculation order
2063 * array.
2064 * @param annot the <CODE>PdfAnnotation</CODE> to be added
2065 */
2066 public void addCalculationOrder(PdfFormField annot) {
2067 pdf.addCalculationOrder(annot);
2068 }
2069
2070 /** Set the signature flags.
2071 * @param f the flags. This flags are ORed with current ones
2072 */
2073 public void setSigFlags(int f) {
2074 pdf.setSigFlags(f);
2075 }
2076
2077 /** Adds a <CODE>PdfAnnotation</CODE> or a <CODE>PdfFormField</CODE>
2078 * to the document. Only the top parent of a <CODE>PdfFormField</CODE>
2079 * needs to be added.
2080 * @param annot the <CODE>PdfAnnotation</CODE> or the <CODE>PdfFormField</CODE> to add
2081 */
2082 public void addAnnotation(PdfAnnotation annot) {
2083 pdf.addAnnotation(annot);
2084 }
2085
2086 void addAnnotation(PdfAnnotation annot, int page) {
2087 addAnnotation(annot);
2088 }
2089
2090 /** Sets the PDF version. Must be used right before the document
2091 * is opened. Valid options are VERSION_1_2, VERSION_1_3,
2092 * VERSION_1_4, VERSION_1_5 and VERSION_1_6. VERSION_1_4 is the default.
2093 * @param version the version number
2094 */
2095 public void setPdfVersion(char version) {
2096 if (HEADER.length > VPOINT)
2097 HEADER[VPOINT] = (byte)version;
2098 }
2099
2100 /** Reorder the pages in the document. A <CODE>null</CODE> argument value
2101 * only returns the number of pages to process. It is
2102 * advisable to issue a <CODE>Document.newPage()</CODE>
2103 * before using this method.
2104 * @return the total number of pages
2105 * @param order an array with the new page sequence. It must have the
2106 * same size as the number of pages.
2107 * @throws DocumentException if all the pages are not present in the array
2108 */
2109 public int reorderPages(int order[]) throws DocumentException {
2110 return root.reorderPages(order);
2111 }
2112
2113 /** Gets the space/character extra spacing ratio for
2114 * fully justified text.
2115 * @return the space/character extra spacing ratio
2116 */
2117 public float getSpaceCharRatio() {
2118 return spaceCharRatio;
2119 }
2120
2121 /** Sets the ratio between the extra word spacing and the extra character spacing
2122 * when the text is fully justified.
2123 * Extra word spacing will grow <CODE>spaceCharRatio</CODE> times more than extra character spacing.
2124 * If the ratio is <CODE>PdfWriter.NO_SPACE_CHAR_RATIO</CODE> then the extra character spacing
2125 * will be zero.
2126 * @param spaceCharRatio the ratio between the extra word spacing and the extra character spacing
2127 */
2128 public void setSpaceCharRatio(float spaceCharRatio) {
2129 if (spaceCharRatio < 0.001f)
2130 this.spaceCharRatio = 0.001f;
2131 else
2132 this.spaceCharRatio = spaceCharRatio;
2133 }
2134
2135 /** Sets the run direction. This is only used as a placeholder
2136 * as it does not affect anything.
2137 * @param runDirection the run direction
2138 */
2139 public void setRunDirection(int runDirection) {
2140 if (runDirection < RUN_DIRECTION_NO_BIDI || runDirection > RUN_DIRECTION_RTL)
2141 throw new RuntimeException("Invalid run direction: " + runDirection);
2142 this.runDirection = runDirection;
2143 }
2144
2145 /** Gets the run direction.
2146 * @return the run direction
2147 */
2148 public int getRunDirection() {
2149 return runDirection;
2150 }
2151
2152 /**
2153 * Sets the display duration for the page (for presentations)
2154 * @param seconds the number of seconds to display the page
2155 */
2156 public void setDuration(int seconds) {
2157 pdf.setDuration(seconds);
2158 }
2159
2160 /**
2161 * Sets the transition for the page
2162 * @param transition the Transition object
2163 */
2164 public void setTransition(PdfTransition transition) {
2165 pdf.setTransition(transition);
2166 }
2167
2168 /** Writes the reader to the document and frees the memory used by it.
2169 * The main use is when concatenating multiple documents to keep the
2170 * memory usage restricted to the current appending document.
2171 * @param reader the <CODE>PdfReader</CODE> to free
2172 * @throws IOException on error
2173 */
2174 public void freeReader(PdfReader reader) throws IOException {
2175 currentPdfReaderInstance = (PdfReaderInstance)importedPages.get(reader);
2176 if (currentPdfReaderInstance == null)
2177 return;
2178 currentPdfReaderInstance.writeAllPages();
2179 currentPdfReaderInstance = null;
2180 importedPages.remove(reader);
2181 }
2182
2183 /** Sets the open and close page additional action.
2184 * @param actionType the action type. It can be <CODE>PdfWriter.PAGE_OPEN</CODE>
2185 * or <CODE>PdfWriter.PAGE_CLOSE</CODE>
2186 * @param action the action to perform
2187 * @throws PdfException if the action type is invalid
2188 */
2189 public void setPageAction(PdfName actionType, PdfAction action) throws PdfException {
2190 if (!actionType.equals(PAGE_OPEN) && !actionType.equals(PAGE_CLOSE))
2191 throw new PdfException("Invalid page additional action type: " + actionType.toString());
2192 pdf.setPageAction(actionType, action);
2193 }
2194
2195 /** Gets the current document size. This size only includes
2196 * the data already writen to the output stream, it does not
2197 * include templates or fonts. It is usefull if used with
2198 * <CODE>freeReader()</CODE> when concatenating many documents
2199 * and an idea of the current size is needed.
2200 * @return the approximate size without fonts or templates
2201 */
2202 public int getCurrentDocumentSize() {
2203 return body.offset() + body.size() * 20 + 0x48;
2204 }
2205
2206 /** Getter for property strictImageSequence.
2207 * @return value of property strictImageSequence
2208 *
2209 */
2210 public boolean isStrictImageSequence() {
2211 return pdf.isStrictImageSequence();
2212 }
2213
2214 /** Sets the image sequence to follow the text in strict order.
2215 * @param strictImageSequence new value of property strictImageSequence
2216 *
2217 */
2218 public void setStrictImageSequence(boolean strictImageSequence) {
2219 pdf.setStrictImageSequence(strictImageSequence);
2220 }
2221
2222 /**
2223 * If you use setPageEmpty(false), invoking newPage() after a blank page will add a newPage.
2224 * @param pageEmpty the state
2225 */
2226 public void setPageEmpty(boolean pageEmpty) {
2227 pdf.setPageEmpty(pageEmpty);
2228 }
2229
2230 /** Gets the info dictionary for changing.
2231 * @return the info dictionary
2232 */
2233 public PdfDictionary getInfo() {
2234 return ((PdfDocument)document).getInfo();
2235 }
2236
2237 /**
2238 * Sets extra keys to the catalog.
2239 * @return the catalog to change
2240 */
2241 public PdfDictionary getExtraCatalog() {
2242 if (extraCatalog == null)
2243 extraCatalog = new PdfDictionary();
2244 return this.extraCatalog;
2245 }
2246
2247 /**
2248 * Sets the document in a suitable way to do page reordering.
2249 */
2250 public void setLinearPageMode() {
2251 root.setLinearMode(null);
2252 }
2253
2254 /** Getter for property group.
2255 * @return Value of property group.
2256 *
2257 */
2258 public PdfDictionary getGroup() {
2259 return this.group;
2260 }
2261
2262 /** Setter for property group.
2263 * @param group New value of property group.
2264 *
2265 */
2266 public void setGroup(PdfDictionary group) {
2267 this.group = group;
2268 }
2269
2270 /**
2271 * Sets the PDFX conformance level. Allowed values are PDFX1A2001 and PDFX32002. It
2272 * must be called before opening the document.
2273 * @param pdfxConformance the conformance level
2274 */
2275 public void setPDFXConformance(int pdfxConformance) {
2276 if (this.pdfxConformance == pdfxConformance)
2277 return;
2278 if (pdf.isOpen())
2279 throw new PdfXConformanceException("PDFX conformance can only be set before opening the document.");
2280 if (crypto != null)
2281 throw new PdfXConformanceException("A PDFX conforming document cannot be encrypted.");
2282 if (pdfxConformance != PDFXNONE)
2283 setPdfVersion(VERSION_1_3);
2284 this.pdfxConformance = pdfxConformance;
2285 }
2286
2287 /**
2288 * Gets the PDFX conformance level.
2289 * @return the PDFX conformance level
2290 */
2291 public int getPDFXConformance() {
2292 return pdfxConformance;
2293 }
2294
2295 static void checkPDFXConformance(PdfWriter writer, int key, Object obj1) {
2296 if (writer == null || writer.pdfxConformance == PDFXNONE)
2297 return;
2298 int conf = writer.pdfxConformance;
2299 switch (key) {
2300 case PDFXKEY_COLOR:
2301 switch (conf) {
2302 case PDFX1A2001:
2303 if (obj1 instanceof ExtendedColor) {
2304 ExtendedColor ec = (ExtendedColor)obj1;
2305 switch (ec.getType()) {
2306 case ExtendedColor.TYPE_CMYK:
2307 case ExtendedColor.TYPE_GRAY:
2308 return;
2309 case ExtendedColor.TYPE_RGB:
2310 throw new PdfXConformanceException("Colorspace RGB is not allowed.");
2311 case ExtendedColor.TYPE_SEPARATION:
2312 SpotColor sc = (SpotColor)ec;
2313 checkPDFXConformance(writer, PDFXKEY_COLOR, sc.getPdfSpotColor().getAlternativeCS());
2314 break;
2315 case ExtendedColor.TYPE_SHADING:
2316 ShadingColor xc = (ShadingColor)ec;
2317 checkPDFXConformance(writer, PDFXKEY_COLOR, xc.getPdfShadingPattern().getShading().getColorSpace());
2318 break;
2319 case ExtendedColor.TYPE_PATTERN:
2320 PatternColor pc = (PatternColor)ec;
2321 checkPDFXConformance(writer, PDFXKEY_COLOR, pc.getPainter().getDefaultColor());
2322 break;
2323 }
2324 }
2325 else if (obj1 instanceof Color)
2326 throw new PdfXConformanceException("Colorspace RGB is not allowed.");
2327 break;
2328 }
2329 break;
2330 case PDFXKEY_CMYK:
2331 break;
2332 case PDFXKEY_RGB:
2333 if (conf == PDFX1A2001)
2334 throw new PdfXConformanceException("Colorspace RGB is not allowed.");
2335 break;
2336 case PDFXKEY_FONT:
2337 if (!((BaseFont)obj1).isEmbedded())
2338 throw new PdfXConformanceException("All the fonts must be embedded.");
2339 break;
2340 case PDFXKEY_IMAGE:
2341 PdfImage image = (PdfImage)obj1;
2342 if (image.get(PdfName.SMASK) != null)
2343 throw new PdfXConformanceException("The /SMask key is not allowed in images.");
2344 switch (conf) {
2345 case PDFX1A2001:
2346 PdfObject cs = image.get(PdfName.COLORSPACE);
2347 if (cs == null)
2348 return;
2349 if (cs.isName()) {
2350 if (PdfName.DEVICERGB.equals(cs))
2351 throw new PdfXConformanceException("Colorspace RGB is not allowed.");
2352 }
2353 else if (cs.isArray()) {
2354 if (PdfName.CALRGB.equals((PdfObject)((PdfArray)cs).getArrayList().get(0)))
2355 throw new PdfXConformanceException("Colorspace CalRGB is not allowed.");
2356 }
2357 break;
2358 }
2359 break;
2360 case PDFXKEY_GSTATE:
2361 PdfDictionary gs = (PdfDictionary)obj1;
2362 PdfObject obj = gs.get(PdfName.BM);
2363 if (obj != null && !PdfGState.BM_NORMAL.equals(obj) && !PdfGState.BM_COMPATIBLE.equals(obj))
2364 throw new PdfXConformanceException("Blend mode " + obj.toString() + " not allowed.");
2365 obj = gs.get(PdfName.CA);
2366 double v = 0.0;
2367 if (obj != null && (v = ((PdfNumber)obj).doubleValue()) != 1.0)
2368 throw new PdfXConformanceException("Transparency is not allowed: /CA = " + v);
2369 obj = gs.get(PdfName.ca);
2370 v = 0.0;
2371 if (obj != null && (v = ((PdfNumber)obj).doubleValue()) != 1.0)
2372 throw new PdfXConformanceException("Transparency is not allowed: /ca = " + v);
2373 break;
2374 case PDFXKEY_LAYER:
2375 throw new PdfXConformanceException("Layers are not allowed.");
2376 }
2377 }
2378
2379 /**
2380 * Sets the values of the output intent dictionary. Null values are allowed to
2381 * suppress any key.
2382 * @param outputConditionIdentifier a value
2383 * @param outputCondition a value
2384 * @param registryName a value
2385 * @param info a value
2386 * @param destOutputProfile a value
2387 * @throws IOException on error
2388 */
2389 public void setOutputIntents(String outputConditionIdentifier, String outputCondition, String registryName, String info, byte destOutputProfile[]) throws IOException {
2390 getExtraCatalog();
2391 PdfDictionary out = new PdfDictionary(PdfName.OUTPUTINTENT);
2392 if (outputCondition != null)
2393 out.put(PdfName.OUTPUTCONDITION, new PdfString(outputCondition, PdfObject.TEXT_UNICODE));
2394 if (outputConditionIdentifier != null)
2395 out.put(PdfName.OUTPUTCONDITIONIDENTIFIER, new PdfString(outputConditionIdentifier, PdfObject.TEXT_UNICODE));
2396 if (registryName != null)
2397 out.put(PdfName.REGISTRYNAME, new PdfString(registryName, PdfObject.TEXT_UNICODE));
2398 if (info != null)
2399 out.put(PdfName.INFO, new PdfString(registryName, PdfObject.TEXT_UNICODE));
2400 if (destOutputProfile != null) {
2401 PdfStream stream = new PdfStream(destOutputProfile);
2402 stream.flateCompress();
2403 out.put(PdfName.DESTOUTPUTPROFILE, addToBody(stream).getIndirectReference());
2404 }
2405 out.put(PdfName.S, PdfName.GTS_PDFX);
2406 extraCatalog.put(PdfName.OUTPUTINTENTS, new PdfArray(out));
2407 }
2408
2409 private static String getNameString(PdfDictionary dic, PdfName key) {
2410 PdfObject obj = PdfReader.getPdfObject(dic.get(key));
2411 if (obj == null || !obj.isString())
2412 return null;
2413 return ((PdfString)obj).toUnicodeString();
2414 }
2415
2416 /**
2417 * Copies the output intent dictionary from other document to this one.
2418 * @param reader the other document
2419 * @param checkExistence <CODE>true</CODE> to just check for the existence of a valid output intent
2420 * dictionary, <CODE>false</CODE> to insert the dictionary if it exists
2421 * @throws IOException on error
2422 * @return <CODE>true</CODE> if the output intent dictionary exists, <CODE>false</CODE>
2423 * otherwise
2424 */
2425 public boolean setOutputIntents(PdfReader reader, boolean checkExistence) throws IOException {
2426 PdfDictionary catalog = reader.getCatalog();
2427 PdfArray outs = (PdfArray)PdfReader.getPdfObject(catalog.get(PdfName.OUTPUTINTENTS));
2428 if (outs == null)
2429 return false;
2430 ArrayList arr = outs.getArrayList();
2431 if (arr.size() == 0)
2432 return false;
2433 PdfDictionary out = (PdfDictionary)PdfReader.getPdfObject((PdfObject)arr.get(0));
2434 PdfObject obj = PdfReader.getPdfObject(out.get(PdfName.S));
2435 if (obj == null || !PdfName.GTS_PDFX.equals(obj))
2436 return false;
2437 if (checkExistence)
2438 return true;
2439 PRStream stream = (PRStream)PdfReader.getPdfObject(out.get(PdfName.DESTOUTPUTPROFILE));
2440 byte destProfile[] = null;
2441 if (stream != null) {
2442 destProfile = PdfReader.getStreamBytes(stream);
2443 }
2444 setOutputIntents(getNameString(out, PdfName.OUTPUTCONDITIONIDENTIFIER), getNameString(out, PdfName.OUTPUTCONDITION),
2445 getNameString(out, PdfName.REGISTRYNAME), getNameString(out, PdfName.INFO), destProfile);
2446 return true;
2447 }
2448
2449 /**
2450 * Sets the page box sizes. Allowed names are: "crop", "trim", "art" and "bleed".
2451 * @param boxName the box size
2452 * @param size the size
2453 */
2454 public void setBoxSize(String boxName, Rectangle size) {
2455 pdf.setBoxSize(boxName, size);
2456 }
2457
2458 /**
2459 * Gets the default colorspaces.
2460 * @return the default colorspaces
2461 */
2462 public PdfDictionary getDefaultColorspace() {
2463 return defaultColorspace;
2464 }
2465
2466 /**
2467 * Sets the default colorspace that will be applied to all the document.
2468 * The colorspace is only applied if another colorspace with the same name
2469 * is not present in the content.
2470 * <p>
2471 * The colorspace is applied immediately when creating templates and at the page
2472 * end for the main document content.
2473 * @param key the name of the colorspace. It can be <CODE>PdfName.DEFAULTGRAY</CODE>, <CODE>PdfName.DEFAULTRGB</CODE>
2474 * or <CODE>PdfName.DEFAULTCMYK</CODE>
2475 * @param cs the colorspace. A <CODE>null</CODE> or <CODE>PdfNull</CODE> removes any colorspace with the same name
2476 */
2477 public void setDefaultColorspace(PdfName key, PdfObject cs) {
2478 if (cs == null || cs.isNull())
2479 defaultColorspace.remove(key);
2480 defaultColorspace.put(key, cs);
2481 }
2482
2483 /**
2484 * Gets the 1.5 compression status.
2485 * @return <code>true</code> if the 1.5 compression is on
2486 */
2487 public boolean isFullCompression() {
2488 return this.fullCompression;
2489 }
2490
2491 /**
2492 * Sets the document's compression to the new 1.5 mode with object streams and xref
2493 * streams. It can be set at any time but once set it can't be unset.
2494 * <p>
2495 * If set before opening the document it will also set the pdf version to 1.5.
2496 */
2497 public void setFullCompression() {
2498 this.fullCompression = true;
2499 setPdfVersion(VERSION_1_5);
2500 }
2501
2502 /**
2503 * Gets the <B>Optional Content Properties Dictionary</B>. Each call fills the dictionary with the current layer
2504 * state. It's advisable to only call this method right before close and do any modifications
2505 * at that time.
2506 * @return the Optional Content Properties Dictionary
2507 */
2508 public PdfOCProperties getOCProperties() {
2509 fillOCProperties(true);
2510 return OCProperties;
2511 }
2512
2513 /**
2514 * Sets a collection of optional content groups whose states are intended to follow
2515 * a "radio button" paradigm. That is, the state of at most one optional
2516 * content group in the array should be ON at a time: if one group is turned
2517 * ON, all others must be turned OFF.
2518 * @param group the radio group
2519 */
2520 public void addOCGRadioGroup(ArrayList group) {
2521 PdfArray ar = new PdfArray();
2522 for (int k = 0; k < group.size(); ++k) {
2523 PdfLayer layer = (PdfLayer)group.get(k);
2524 if (layer.getTitle() == null)
2525 ar.add(layer.getRef());
2526 }
2527 if (ar.size() == 0)
2528 return;
2529 OCGRadioGroup.add(ar);
2530 }
2531
2532 /**
2533 * Sets the the thumbnail image for the current page.
2534 * @param image the image
2535 * @throws PdfException on error
2536 * @throws DocumentException or error
2537 */
2538 public void setThumbnail(Image image) throws PdfException, DocumentException {
2539 pdf.setThumbnail(image);
2540 }
2541
2542 }