1 /*
2 * Copyright 2003 by Paulo Soares.
3 *
4 * The contents of this file are subject to the Mozilla Public License Version 1.1
5 * (the "License"); you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
7 *
8 * Software distributed under the License is distributed on an "AS IS" basis,
9 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
10 * for the specific language governing rights and limitations under the License.
11 *
12 * The Original Code is 'iText, a free JAVA-PDF library'.
13 *
14 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
15 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
16 * All Rights Reserved.
17 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
18 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
19 *
20 * Contributor(s): all the names of the contributors are added in the source code
21 * where applicable.
22 *
23 * Alternatively, the contents of this file may be used under the terms of the
24 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
25 * provisions of LGPL are applicable instead of those above. If you wish to
26 * allow use of your version of this file only under the terms of the LGPL
27 * License and not to allow others to use your version of this file under
28 * the MPL, indicate your decision by deleting the provisions above and
29 * replace them with the notice and other provisions required by the LGPL.
30 * If you do not delete the provisions above, a recipient may use your version
31 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
32 *
33 * This library is free software; you can redistribute it and/or modify it
34 * under the terms of the MPL as stated above or under the terms of the GNU
35 * Library General Public License as published by the Free Software Foundation;
36 * either version 2 of the License, or any later version.
37 *
38 * This library is distributed in the hope that it will be useful, but WITHOUT
39 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
40 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
41 * details.
42 *
43 * If you didn't download this code from the following link, you should check if
44 * you aren't using an obsolete version:
45 * http://www.lowagie.com/iText/
46 */
47 package com.lowagie.text.pdf;
48
49 import java.io.IOException;
50 import java.io.OutputStream;
51 import java.util.ArrayList;
52 import java.util.HashMap;
53 import java.util.HashSet;
54 import java.util.Iterator;
55 import java.util.Map;
56
57 import com.lowagie.text.DocumentException;
58 import com.lowagie.text.ExceptionConverter;
59 import com.lowagie.text.Image;
60 import com.lowagie.text.Rectangle;
61 import com.lowagie.text.pdf.collection.PdfCollection;
62 import com.lowagie.text.pdf.interfaces.PdfViewerPreferences;
63 import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
64 import com.lowagie.text.xml.xmp.XmpReader;
65
66 class PdfStamperImp extends PdfWriter {
67 HashMap readers2intrefs = new HashMap();
68 HashMap readers2file = new HashMap();
69 RandomAccessFileOrArray file;
70 PdfReader reader;
71 IntHashtable myXref = new IntHashtable();
72 /** Integer(page number) -> PageStamp */
73 HashMap pagesToContent = new HashMap();
74 boolean closed = false;
75 /** Holds value of property rotateContents. */
76 private boolean rotateContents = true;
77 protected AcroFields acroFields;
78 protected boolean flat = false;
79 protected boolean flatFreeText = false;
80 protected int namePtr[] = {0};
81 protected HashSet partialFlattening = new HashSet();
82 protected boolean useVp = false;
83 protected PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
84 protected HashMap fieldTemplates = new HashMap();
85 protected boolean fieldsAdded = false;
86 protected int sigFlags = 0;
87 protected boolean append;
88 protected IntHashtable marked;
89 protected int initialXrefSize;
90 protected PdfAction openAction;
91
92 /** Creates new PdfStamperImp.
93 * @param reader the read PDF
94 * @param os the output destination
95 * @param pdfVersion the new pdf version or '\0' to keep the same version as the original
96 * document
97 * @param append
98 * @throws DocumentException on error
99 * @throws IOException
100 */
101 PdfStamperImp(PdfReader reader, OutputStream os, char pdfVersion, boolean append) throws DocumentException, IOException {
102 super(new PdfDocument(), os);
103 if (!reader.isOpenedWithFullPermissions())
104 throw new IllegalArgumentException("PdfReader not opened with owner password");
105 if (reader.isTampered())
106 throw new DocumentException("The original document was reused. Read it again from file.");
107 reader.setTampered(true);
108 this.reader = reader;
109 file = reader.getSafeFile();
110 this.append = append;
111 if (append) {
112 if (reader.isRebuilt())
113 throw new DocumentException("Append mode requires a document without errors even if recovery was possible.");
114 if (reader.isEncrypted())
115 crypto = new PdfEncryption(reader.getDecrypt());
116 pdf_version.setAppendmode(true);
117 file.reOpen();
118 byte buf[] = new byte[8192];
119 int n;
120 while ((n = file.read(buf)) > 0)
121 this.os.write(buf, 0, n);
122 file.close();
123 prevxref = reader.getLastXref();
124 reader.setAppendable(true);
125 }
126 else {
127 if (pdfVersion == 0)
128 super.setPdfVersion(reader.getPdfVersion());
129 else
130 super.setPdfVersion(pdfVersion);
131 }
132 super.open();
133 pdf.addWriter(this);
134 if (append) {
135 body.setRefnum(reader.getXrefSize());
136 marked = new IntHashtable();
137 if (reader.isNewXrefType())
138 fullCompression = true;
139 if (reader.isHybridXref())
140 fullCompression = false;
141 }
142 initialXrefSize = reader.getXrefSize();
143 }
144
145 void close(HashMap moreInfo) throws IOException {
146 if (closed)
147 return;
148 if (useVp) {
149 reader.setViewerPreferences(viewerPreferences);
150 markUsed(reader.getTrailer().get(PdfName.ROOT));
151 }
152 if (flat)
153 flatFields();
154 if (flatFreeText)
155 flatFreeTextFields();
156 addFieldResources();
157 PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(reader.getCatalog().get(PdfName.ACROFORM), reader.getCatalog());
158 if (acroFields != null && acroFields.getXfa().isChanged()) {
159 markUsed(acroForm);
160 if (!flat)
161 acroFields.getXfa().setXfa(this);
162 }
163 if (sigFlags != 0) {
164 if (acroForm != null) {
165 acroForm.put(PdfName.SIGFLAGS, new PdfNumber(sigFlags));
166 markUsed(acroForm);
167 }
168 }
169 closed = true;
170 addSharedObjectsToBody();
171 setOutlines();
172 setJavaScript();
173 addFileAttachments();
174 PdfDictionary catalog = reader.getCatalog();
175 if (openAction != null) {
176 catalog.put(PdfName.OPENACTION, openAction);
177 }
178 if (pdf.pageLabels != null)
179 catalog.put(PdfName.PAGELABELS, pdf.pageLabels.getDictionary(this));
180 byte[] altMetadata = null;
181 PdfObject xmpo = PdfReader.getPdfObject(catalog.get(PdfName.METADATA));
182 if (xmpo != null && xmpo.isStream()) {
183 altMetadata = PdfReader.getStreamBytesRaw((PRStream)xmpo);
184 PdfReader.killIndirect(catalog.get(PdfName.METADATA));
185 }
186 if (xmpMetadata != null) {
187 altMetadata = xmpMetadata;
188 }
189 // if there is XMP data to add: add it
190 PdfString date = new PdfDate();
191 if (altMetadata != null) {
192 XmpReader xmpr = new XmpReader(altMetadata);
193 xmpr.replace("http://ns.adobe.com/xap/1.0/", "ModifyDate", date.toString());
194 xmpr.replace("http://ns.adobe.com/xap/1.0/", "MetadataDate", date.toString());
195 PdfStream xmp = new PdfStream(xmpr.serializeDoc());
196 xmp.put(PdfName.TYPE, PdfName.METADATA);
197 xmp.put(PdfName.SUBTYPE, PdfName.XML);
198 if (crypto != null && !crypto.isMetadataEncrypted()) {
199 PdfArray ar = new PdfArray();
200 ar.add(PdfName.CRYPT);
201 xmp.put(PdfName.FILTER, ar);
202 }
203 if (append && xmpo != null) {
204 body.add(xmp, xmpo.getIndRef());
205 }
206 else {
207 catalog.put(PdfName.METADATA, body.add(xmp).getIndirectReference());
208 markUsed(catalog);
209 }
210 }
211 if (!documentOCG.isEmpty()) {
212 fillOCProperties(false);
213 PdfDictionary ocdict = catalog.getAsDict(PdfName.OCPROPERTIES);
214 if (ocdict == null) {
215 reader.getCatalog().put(PdfName.OCPROPERTIES, OCProperties);
216 }
217 else {
218 ocdict.put(PdfName.OCGS, OCProperties.get(PdfName.OCGS));
219 PdfDictionary ddict = ocdict.getAsDict(PdfName.D);
220 ddict.put(PdfName.ORDER, OCProperties.getAsDict(PdfName.D).get(PdfName.ORDER));
221 ddict.put(PdfName.RBGROUPS, OCProperties.getAsDict(PdfName.D).get(PdfName.RBGROUPS));
222 ddict.put(PdfName.OFF, OCProperties.getAsDict(PdfName.D).get(PdfName.OFF));
223 ddict.put(PdfName.AS, OCProperties.getAsDict(PdfName.D).get(PdfName.AS));
224 }
225 }
226 PRIndirectReference iInfo = null;
227 try {
228 file.reOpen();
229 alterContents();
230 iInfo = (PRIndirectReference)reader.trailer.get(PdfName.INFO);
231 int skip = -1;
232 if (iInfo != null)
233 skip = iInfo.getNumber();
234 int rootN = ((PRIndirectReference)reader.trailer.get(PdfName.ROOT)).getNumber();
235 if (append) {
236 int keys[] = marked.getKeys();
237 for (int k = 0; k < keys.length; ++k) {
238 int j = keys[k];
239 PdfObject obj = reader.getPdfObjectRelease(j);
240 if (obj != null && skip != j && j < initialXrefSize) {
241 addToBody(obj, j, j != rootN);
242 }
243 }
244 for (int k = initialXrefSize; k < reader.getXrefSize(); ++k) {
245 PdfObject obj = reader.getPdfObject(k);
246 if (obj != null) {
247 addToBody(obj, getNewObjectNumber(reader, k, 0));
248 }
249 }
250 }
251 else {
252 for (int k = 1; k < reader.getXrefSize(); ++k) {
253 PdfObject obj = reader.getPdfObjectRelease(k);
254 if (obj != null && skip != k) {
255 addToBody(obj, getNewObjectNumber(reader, k, 0), k != rootN);
256 }
257 }
258 }
259 }
260 finally {
261 try {
262 file.close();
263 }
264 catch (Exception e) {
265 // empty on purpose
266 }
267 }
268 PdfIndirectReference encryption = null;
269 PdfObject fileID = null;
270 if (crypto != null) {
271 if (append) {
272 encryption = reader.getCryptoRef();
273 }
274 else {
275 PdfIndirectObject encryptionObject = addToBody(crypto.getEncryptionDictionary(), false);
276 encryption = encryptionObject.getIndirectReference();
277 }
278 fileID = crypto.getFileID();
279 }
280 else
281 fileID = PdfEncryption.createInfoId(PdfEncryption.createDocumentId());
282 PRIndirectReference iRoot = (PRIndirectReference)reader.trailer.get(PdfName.ROOT);
283 PdfIndirectReference root = new PdfIndirectReference(0, getNewObjectNumber(reader, iRoot.getNumber(), 0));
284 PdfIndirectReference info = null;
285 PdfDictionary oldInfo = (PdfDictionary)PdfReader.getPdfObject(iInfo);
286 PdfDictionary newInfo = new PdfDictionary();
287 if (oldInfo != null) {
288 for (Iterator i = oldInfo.getKeys().iterator(); i.hasNext();) {
289 PdfName key = (PdfName)i.next();
290 PdfObject value = PdfReader.getPdfObject(oldInfo.get(key));
291 newInfo.put(key, value);
292 }
293 }
294 if (moreInfo != null) {
295 for (Iterator i = moreInfo.entrySet().iterator(); i.hasNext();) {
296 Map.Entry entry = (Map.Entry) i.next();
297 String key = (String) entry.getKey();
298 PdfName keyName = new PdfName(key);
299 String value = (String) entry.getValue();
300 if (value == null)
301 newInfo.remove(keyName);
302 else
303 newInfo.put(keyName, new PdfString(value, PdfObject.TEXT_UNICODE));
304 }
305 }
306 newInfo.put(PdfName.MODDATE, date);
307 if (append) {
308 if (iInfo == null)
309 info = addToBody(newInfo, false).getIndirectReference();
310 else
311 info = addToBody(newInfo, iInfo.getNumber(), false).getIndirectReference();
312 }
313 else {
314 info = addToBody(newInfo, false).getIndirectReference();
315 }
316 // write the cross-reference table of the body
317 body.writeCrossReferenceTable(os, root, info, encryption, fileID, prevxref);
318 if (fullCompression) {
319 os.write(getISOBytes("startxref\n"));
320 os.write(getISOBytes(String.valueOf(body.offset())));
321 os.write(getISOBytes("\n%%EOF\n"));
322 }
323 else {
324 PdfTrailer trailer = new PdfTrailer(body.size(),
325 body.offset(),
326 root,
327 info,
328 encryption,
329 fileID, prevxref);
330 trailer.toPdf(this, os);
331 }
332 os.flush();
333 if (isCloseStream())
334 os.close();
335 reader.close();
336 }
337
338 void applyRotation(PdfDictionary pageN, ByteBuffer out) {
339 if (!rotateContents)
340 return;
341 Rectangle page = reader.getPageSizeWithRotation(pageN);
342 int rotation = page.getRotation();
343 switch (rotation) {
344 case 90:
345 out.append(PdfContents.ROTATE90);
346 out.append(page.getTop());
347 out.append(' ').append('0').append(PdfContents.ROTATEFINAL);
348 break;
349 case 180:
350 out.append(PdfContents.ROTATE180);
351 out.append(page.getRight());
352 out.append(' ');
353 out.append(page.getTop());
354 out.append(PdfContents.ROTATEFINAL);
355 break;
356 case 270:
357 out.append(PdfContents.ROTATE270);
358 out.append('0').append(' ');
359 out.append(page.getRight());
360 out.append(PdfContents.ROTATEFINAL);
361 break;
362 }
363 }
364
365 void alterContents() throws IOException {
366 for (Iterator i = pagesToContent.values().iterator(); i.hasNext();) {
367 PageStamp ps = (PageStamp)i.next();
368 PdfDictionary pageN = ps.pageN;
369 markUsed(pageN);
370 PdfArray ar = null;
371 PdfObject content = PdfReader.getPdfObject(pageN.get(PdfName.CONTENTS), pageN);
372 if (content == null) {
373 ar = new PdfArray();
374 pageN.put(PdfName.CONTENTS, ar);
375 }
376 else if (content.isArray()) {
377 ar = (PdfArray)content;
378 markUsed(ar);
379 }
380 else if (content.isStream()) {
381 ar = new PdfArray();
382 ar.add(pageN.get(PdfName.CONTENTS));
383 pageN.put(PdfName.CONTENTS, ar);
384 }
385 else {
386 ar = new PdfArray();
387 pageN.put(PdfName.CONTENTS, ar);
388 }
389 ByteBuffer out = new ByteBuffer();
390 if (ps.under != null) {
391 out.append(PdfContents.SAVESTATE);
392 applyRotation(pageN, out);
393 out.append(ps.under.getInternalBuffer());
394 out.append(PdfContents.RESTORESTATE);
395 }
396 if (ps.over != null)
397 out.append(PdfContents.SAVESTATE);
398 PdfStream stream = new PdfStream(out.toByteArray());
399 stream.flateCompress(compressionLevel);
400 ar.addFirst(addToBody(stream).getIndirectReference());
401 out.reset();
402 if (ps.over != null) {
403 out.append(' ');
404 out.append(PdfContents.RESTORESTATE);
405 ByteBuffer buf = ps.over.getInternalBuffer();
406 out.append(buf.getBuffer(), 0, ps.replacePoint);
407 out.append(PdfContents.SAVESTATE);
408 applyRotation(pageN, out);
409 out.append(buf.getBuffer(), ps.replacePoint, buf.size() - ps.replacePoint);
410 out.append(PdfContents.RESTORESTATE);
411 stream = new PdfStream(out.toByteArray());
412 stream.flateCompress(compressionLevel);
413 ar.add(addToBody(stream).getIndirectReference());
414 }
415 alterResources(ps);
416 }
417 }
418
419 void alterResources(PageStamp ps) {
420 ps.pageN.put(PdfName.RESOURCES, ps.pageResources.getResources());
421 }
422
423 protected int getNewObjectNumber(PdfReader reader, int number, int generation) {
424 IntHashtable ref = (IntHashtable)readers2intrefs.get(reader);
425 if (ref != null) {
426 int n = ref.get(number);
427 if (n == 0) {
428 n = getIndirectReferenceNumber();
429 ref.put(number, n);
430 }
431 return n;
432 }
433 if (currentPdfReaderInstance == null) {
434 if (append && number < initialXrefSize)
435 return number;
436 int n = myXref.get(number);
437 if (n == 0) {
438 n = getIndirectReferenceNumber();
439 myXref.put(number, n);
440 }
441 return n;
442 }
443 else
444 return currentPdfReaderInstance.getNewObjectNumber(number, generation);
445 }
446
447 RandomAccessFileOrArray getReaderFile(PdfReader reader) {
448 if (readers2intrefs.containsKey(reader)) {
449 RandomAccessFileOrArray raf = (RandomAccessFileOrArray)readers2file.get(reader);
450 if (raf != null)
451 return raf;
452 return reader.getSafeFile();
453 }
454 if (currentPdfReaderInstance == null)
455 return file;
456 else
457 return currentPdfReaderInstance.getReaderFile();
458 }
459
460 /**
461 * @param reader
462 * @param openFile
463 * @throws IOException
464 */
465 public void registerReader(PdfReader reader, boolean openFile) throws IOException {
466 if (readers2intrefs.containsKey(reader))
467 return;
468 readers2intrefs.put(reader, new IntHashtable());
469 if (openFile) {
470 RandomAccessFileOrArray raf = reader.getSafeFile();
471 readers2file.put(reader, raf);
472 raf.reOpen();
473 }
474 }
475
476 /**
477 * @param reader
478 */
479 public void unRegisterReader(PdfReader reader) {
480 if (!readers2intrefs.containsKey(reader))
481 return;
482 readers2intrefs.remove(reader);
483 RandomAccessFileOrArray raf = (RandomAccessFileOrArray)readers2file.get(reader);
484 if (raf == null)
485 return;
486 readers2file.remove(reader);
487 try{raf.close();}catch(Exception e){}
488 }
489
490 static void findAllObjects(PdfReader reader, PdfObject obj, IntHashtable hits) {
491 if (obj == null)
492 return;
493 switch (obj.type()) {
494 case PdfObject.INDIRECT:
495 PRIndirectReference iref = (PRIndirectReference)obj;
496 if (reader != iref.getReader())
497 return;
498 if (hits.containsKey(iref.getNumber()))
499 return;
500 hits.put(iref.getNumber(), 1);
501 findAllObjects(reader, PdfReader.getPdfObject(obj), hits);
502 return;
503 case PdfObject.ARRAY:
504 ArrayList lst = ((PdfArray)obj).getArrayList();
505 for (int k = 0; k < lst.size(); ++k) {
506 findAllObjects(reader, (PdfObject)lst.get(k), hits);
507 }
508 return;
509 case PdfObject.DICTIONARY:
510 case PdfObject.STREAM:
511 PdfDictionary dic = (PdfDictionary)obj;
512 for (Iterator it = dic.getKeys().iterator(); it.hasNext();) {
513 PdfName name = (PdfName)it.next();
514 findAllObjects(reader, dic.get(name), hits);
515 }
516 return;
517 }
518 }
519
520 /**
521 * @param fdf
522 * @throws IOException
523 */
524 public void addComments(FdfReader fdf) throws IOException{
525 if (readers2intrefs.containsKey(fdf))
526 return;
527 PdfDictionary catalog = fdf.getCatalog();
528 catalog = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.FDF));
529 if (catalog == null)
530 return;
531 PdfArray annots = (PdfArray)PdfReader.getPdfObject(catalog.get(PdfName.ANNOTS));
532 if (annots == null || annots.size() == 0)
533 return;
534 registerReader(fdf, false);
535 IntHashtable hits = new IntHashtable();
536 HashMap irt = new HashMap();
537 ArrayList an = new ArrayList();
538 ArrayList ar = annots.getArrayList();
539 for (int k = 0; k < ar.size(); ++k) {
540 PdfObject obj = (PdfObject)ar.get(k);
541 PdfDictionary annot = (PdfDictionary)PdfReader.getPdfObject(obj);
542 PdfNumber page = (PdfNumber)PdfReader.getPdfObject(annot.get(PdfName.PAGE));
543 if (page == null || page.intValue() >= reader.getNumberOfPages())
544 continue;
545 findAllObjects(fdf, obj, hits);
546 an.add(obj);
547 if (obj.type() == PdfObject.INDIRECT) {
548 PdfObject nm = PdfReader.getPdfObject(annot.get(PdfName.NM));
549 if (nm != null && nm.type() == PdfObject.STRING)
550 irt.put(nm.toString(), obj);
551 }
552 }
553 int arhits[] = hits.getKeys();
554 for (int k = 0; k < arhits.length; ++k) {
555 int n = arhits[k];
556 PdfObject obj = fdf.getPdfObject(n);
557 if (obj.type() == PdfObject.DICTIONARY) {
558 PdfObject str = PdfReader.getPdfObject(((PdfDictionary)obj).get(PdfName.IRT));
559 if (str != null && str.type() == PdfObject.STRING) {
560 PdfObject i = (PdfObject)irt.get(str.toString());
561 if (i != null) {
562 PdfDictionary dic2 = new PdfDictionary();
563 dic2.merge((PdfDictionary)obj);
564 dic2.put(PdfName.IRT, i);
565 obj = dic2;
566 }
567 }
568 }
569 addToBody(obj, getNewObjectNumber(fdf, n, 0));
570 }
571 for (int k = 0; k < an.size(); ++k) {
572 PdfObject obj = (PdfObject)an.get(k);
573 PdfDictionary annot = (PdfDictionary)PdfReader.getPdfObject(obj);
574 PdfNumber page = (PdfNumber)PdfReader.getPdfObject(annot.get(PdfName.PAGE));
575 PdfDictionary dic = reader.getPageN(page.intValue() + 1);
576 PdfArray annotsp = (PdfArray)PdfReader.getPdfObject(dic.get(PdfName.ANNOTS), dic);
577 if (annotsp == null) {
578 annotsp = new PdfArray();
579 dic.put(PdfName.ANNOTS, annotsp);
580 markUsed(dic);
581 }
582 markUsed(annotsp);
583 annotsp.add(obj);
584 }
585 }
586
587 PageStamp getPageStamp(int pageNum) {
588 PdfDictionary pageN = reader.getPageN(pageNum);
589 PageStamp ps = (PageStamp)pagesToContent.get(pageN);
590 if (ps == null) {
591 ps = new PageStamp(this, reader, pageN);
592 pagesToContent.put(pageN, ps);
593 }
594 return ps;
595 }
596
597 PdfContentByte getUnderContent(int pageNum) {
598 if (pageNum < 1 || pageNum > reader.getNumberOfPages())
599 return null;
600 PageStamp ps = getPageStamp(pageNum);
601 if (ps.under == null)
602 ps.under = new StampContent(this, ps);
603 return ps.under;
604 }
605
606 PdfContentByte getOverContent(int pageNum) {
607 if (pageNum < 1 || pageNum > reader.getNumberOfPages())
608 return null;
609 PageStamp ps = getPageStamp(pageNum);
610 if (ps.over == null)
611 ps.over = new StampContent(this, ps);
612 return ps.over;
613 }
614
615 void correctAcroFieldPages(int page) {
616 if (acroFields == null)
617 return;
618 if (page > reader.getNumberOfPages())
619 return;
620 HashMap fields = acroFields.getFields();
621 for (Iterator it = fields.values().iterator(); it.hasNext();) {
622 AcroFields.Item item = (AcroFields.Item)it.next();
623 ArrayList pages = item.page;
624 for (int k = 0; k < pages.size(); ++k) {
625 int p = ((Integer)pages.get(k)).intValue();
626 if (p >= page)
627 pages.set(k, new Integer(p + 1));
628 }
629 }
630 }
631
632 private static void moveRectangle(PdfDictionary dic2, PdfReader r, int pageImported, PdfName key, String name) {
633 Rectangle m = r.getBoxSize(pageImported, name);
634 if (m == null)
635 dic2.remove(key);
636 else
637 dic2.put(key, new PdfRectangle(m));
638 }
639
640 void replacePage(PdfReader r, int pageImported, int pageReplaced) {
641 PdfDictionary pageN = reader.getPageN(pageReplaced);
642 if (pagesToContent.containsKey(pageN))
643 throw new IllegalStateException("This page cannot be replaced: new content was already added");
644 PdfImportedPage p = getImportedPage(r, pageImported);
645 PdfDictionary dic2 = reader.getPageNRelease(pageReplaced);
646 dic2.remove(PdfName.RESOURCES);
647 dic2.remove(PdfName.CONTENTS);
648 moveRectangle(dic2, r, pageImported, PdfName.MEDIABOX, "media");
649 moveRectangle(dic2, r, pageImported, PdfName.CROPBOX, "crop");
650 moveRectangle(dic2, r, pageImported, PdfName.TRIMBOX, "trim");
651 moveRectangle(dic2, r, pageImported, PdfName.ARTBOX, "art");
652 moveRectangle(dic2, r, pageImported, PdfName.BLEEDBOX, "bleed");
653 dic2.put(PdfName.ROTATE, new PdfNumber(r.getPageRotation(pageImported)));
654 PdfContentByte cb = getOverContent(pageReplaced);
655 cb.addTemplate(p, 0, 0);
656 PageStamp ps = (PageStamp)pagesToContent.get(pageN);
657 ps.replacePoint = ps.over.getInternalBuffer().size();
658 }
659
660 void insertPage(int pageNumber, Rectangle mediabox) {
661 Rectangle media = new Rectangle(mediabox);
662 int rotation = media.getRotation() % 360;
663 PdfDictionary page = new PdfDictionary(PdfName.PAGE);
664 PdfDictionary resources = new PdfDictionary();
665 PdfArray procset = new PdfArray();
666 procset.add(PdfName.PDF);
667 procset.add(PdfName.TEXT);
668 procset.add(PdfName.IMAGEB);
669 procset.add(PdfName.IMAGEC);
670 procset.add(PdfName.IMAGEI);
671 resources.put(PdfName.PROCSET, procset);
672 page.put(PdfName.RESOURCES, resources);
673 page.put(PdfName.ROTATE, new PdfNumber(rotation));
674 page.put(PdfName.MEDIABOX, new PdfRectangle(media, rotation));
675 PRIndirectReference pref = reader.addPdfObject(page);
676 PdfDictionary parent;
677 PRIndirectReference parentRef;
678 if (pageNumber > reader.getNumberOfPages()) {
679 PdfDictionary lastPage = reader.getPageNRelease(reader.getNumberOfPages());
680 parentRef = (PRIndirectReference)lastPage.get(PdfName.PARENT);
681 parentRef = new PRIndirectReference(reader, parentRef.getNumber());
682 parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
683 PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
684 kids.add(pref);
685 markUsed(kids);
686 reader.pageRefs.insertPage(pageNumber, pref);
687 }
688 else {
689 if (pageNumber < 1)
690 pageNumber = 1;
691 PdfDictionary firstPage = reader.getPageN(pageNumber);
692 PRIndirectReference firstPageRef = reader.getPageOrigRef(pageNumber);
693 reader.releasePage(pageNumber);
694 parentRef = (PRIndirectReference)firstPage.get(PdfName.PARENT);
695 parentRef = new PRIndirectReference(reader, parentRef.getNumber());
696 parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
697 PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS), parent);
698 ArrayList ar = kids.getArrayList();
699 int len = ar.size();
700 int num = firstPageRef.getNumber();
701 for (int k = 0; k < len; ++k) {
702 PRIndirectReference cur = (PRIndirectReference)ar.get(k);
703 if (num == cur.getNumber()) {
704 ar.add(k, pref);
705 break;
706 }
707 }
708 if (len == ar.size())
709 throw new RuntimeException("Internal inconsistence.");
710 markUsed(kids);
711 reader.pageRefs.insertPage(pageNumber, pref);
712 correctAcroFieldPages(pageNumber);
713 }
714 page.put(PdfName.PARENT, parentRef);
715 while (parent != null) {
716 markUsed(parent);
717 PdfNumber count = (PdfNumber)PdfReader.getPdfObjectRelease(parent.get(PdfName.COUNT));
718 parent.put(PdfName.COUNT, new PdfNumber(count.intValue() + 1));
719 parent = (PdfDictionary)PdfReader.getPdfObject(parent.get(PdfName.PARENT));
720 }
721 }
722
723 /** Getter for property rotateContents.
724 * @return Value of property rotateContents.
725 *
726 */
727 boolean isRotateContents() {
728 return this.rotateContents;
729 }
730
731 /** Setter for property rotateContents.
732 * @param rotateContents New value of property rotateContents.
733 *
734 */
735 void setRotateContents(boolean rotateContents) {
736 this.rotateContents = rotateContents;
737 }
738
739 boolean isContentWritten() {
740 return body.size() > 1;
741 }
742
743 AcroFields getAcroFields() {
744 if (acroFields == null) {
745 acroFields = new AcroFields(reader, this);
746 }
747 return acroFields;
748 }
749
750 void setFormFlattening(boolean flat) {
751 this.flat = flat;
752 }
753
754 void setFreeTextFlattening(boolean flat) {
755 this.flatFreeText = flat;
756 }
757
758 boolean partialFormFlattening(String name) {
759 getAcroFields();
760 if (acroFields.getXfa().isXfaPresent())
761 throw new UnsupportedOperationException("Partial form flattening is not supported with XFA forms.");
762 if (!acroFields.getFields().containsKey(name))
763 return false;
764 partialFlattening.add(name);
765 return true;
766 }
767
768 void flatFields() {
769 if (append)
770 throw new IllegalArgumentException("Field flattening is not supported in append mode.");
771 getAcroFields();
772 HashMap fields = acroFields.getFields();
773 if (fieldsAdded && partialFlattening.isEmpty()) {
774 for (Iterator i = fields.keySet().iterator(); i.hasNext();) {
775 partialFlattening.add(i.next());
776 }
777 }
778 PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(reader.getCatalog().get(PdfName.ACROFORM));
779 ArrayList acroFds = null;
780 if (acroForm != null) {
781 PdfArray array = (PdfArray)PdfReader.getPdfObject(acroForm.get(PdfName.FIELDS), acroForm);
782 if (array != null)
783 acroFds = array.getArrayList();
784 }
785 for (Iterator i = fields.entrySet().iterator(); i.hasNext();) {
786 Map.Entry entry = (Map.Entry) i.next();
787 String name = (String) entry.getKey();
788 if (!partialFlattening.isEmpty() && !partialFlattening.contains(name))
789 continue;
790 AcroFields.Item item = (AcroFields.Item) entry.getValue();
791 for (int k = 0; k < item.merged.size(); ++k) {
792 PdfDictionary merged = (PdfDictionary)item.merged.get(k);
793 PdfNumber ff = (PdfNumber)PdfReader.getPdfObject(merged.get(PdfName.F));
794 int flags = 0;
795 if (ff != null)
796 flags = ff.intValue();
797 int page = ((Integer)item.page.get(k)).intValue();
798 PdfDictionary appDic = (PdfDictionary)PdfReader.getPdfObject(merged.get(PdfName.AP));
799 if (appDic != null && (flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) == 0) {
800 PdfObject obj = appDic.get(PdfName.N);
801 PdfAppearance app = null;
802 if (obj != null) {
803 PdfObject objReal = PdfReader.getPdfObject(obj);
804 if (obj instanceof PdfIndirectReference && !obj.isIndirect())
805 app = new PdfAppearance((PdfIndirectReference)obj);
806 else if (objReal instanceof PdfStream) {
807 ((PdfDictionary)objReal).put(PdfName.SUBTYPE, PdfName.FORM);
808 app = new PdfAppearance((PdfIndirectReference)obj);
809 }
810 else {
811 if (objReal != null && objReal.isDictionary()) {
812 PdfName as = (PdfName)PdfReader.getPdfObject(merged.get(PdfName.AS));
813 if (as != null) {
814 PdfIndirectReference iref = (PdfIndirectReference)((PdfDictionary)objReal).get(as);
815 if (iref != null) {
816 app = new PdfAppearance(iref);
817 if (iref.isIndirect()) {
818 objReal = PdfReader.getPdfObject(iref);
819 ((PdfDictionary)objReal).put(PdfName.SUBTYPE, PdfName.FORM);
820 }
821 }
822 }
823 }
824 }
825 }
826 if (app != null) {
827 Rectangle box = PdfReader.getNormalizedRectangle((PdfArray)PdfReader.getPdfObject(merged.get(PdfName.RECT)));
828 PdfContentByte cb = getOverContent(page);
829 cb.setLiteral("Q ");
830 cb.addTemplate(app, box.getLeft(), box.getBottom());
831 cb.setLiteral("q ");
832 }
833 }
834 if (partialFlattening.isEmpty())
835 continue;
836 PdfDictionary pageDic = reader.getPageN(page);
837 PdfArray annots = (PdfArray)PdfReader.getPdfObject(pageDic.get(PdfName.ANNOTS));
838 if (annots == null)
839 continue;
840 ArrayList ar = annots.getArrayList();
841 for (int idx = 0; idx < ar.size(); ++idx) {
842 PdfObject ran = (PdfObject)ar.get(idx);
843 if (!ran.isIndirect())
844 continue;
845 PdfObject ran2 = (PdfObject)item.widget_refs.get(k);
846 if (!ran2.isIndirect())
847 continue;
848 if (((PRIndirectReference)ran).getNumber() == ((PRIndirectReference)ran2).getNumber()) {
849 ar.remove(idx--);
850 PRIndirectReference wdref = (PRIndirectReference)ran2;
851 while (true) {
852 PdfDictionary wd = (PdfDictionary)PdfReader.getPdfObject(wdref);
853 PRIndirectReference parentRef = (PRIndirectReference)wd.get(PdfName.PARENT);
854 PdfReader.killIndirect(wdref);
855 if (parentRef == null) { // reached AcroForm
856 for (int fr = 0; fr < acroFds.size(); ++fr) {
857 PdfObject h = (PdfObject)acroFds.get(fr);
858 if (h.isIndirect() && ((PRIndirectReference)h).getNumber() == wdref.getNumber()) {
859 acroFds.remove(fr);
860 --fr;
861 }
862 }
863 break;
864 }
865 PdfDictionary parent = (PdfDictionary)PdfReader.getPdfObject(parentRef);
866 PdfArray kids = (PdfArray)PdfReader.getPdfObject(parent.get(PdfName.KIDS));
867 ArrayList kar = kids.getArrayList();
868 for (int fr = 0; fr < kar.size(); ++fr) {
869 PdfObject h = (PdfObject)kar.get(fr);
870 if (h.isIndirect() && ((PRIndirectReference)h).getNumber() == wdref.getNumber()) {
871 kar.remove(fr);
872 --fr;
873 }
874 }
875 if (!kar.isEmpty())
876 break;
877 wdref = parentRef;
878 }
879 }
880 }
881 if (ar.isEmpty()) {
882 PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
883 pageDic.remove(PdfName.ANNOTS);
884 }
885 }
886 }
887 if (!fieldsAdded && partialFlattening.isEmpty()) {
888 for (int page = 1; page <= reader.getNumberOfPages(); ++page) {
889 PdfDictionary pageDic = reader.getPageN(page);
890 PdfArray annots = (PdfArray)PdfReader.getPdfObject(pageDic.get(PdfName.ANNOTS));
891 if (annots == null)
892 continue;
893 ArrayList ar = annots.getArrayList();
894 for (int idx = 0; idx < ar.size(); ++idx) {
895 PdfObject annoto = PdfReader.getPdfObject((PdfObject)ar.get(idx));
896 if ((annoto instanceof PdfIndirectReference) && !annoto.isIndirect())
897 continue;
898 if (!annoto.isDictionary() || PdfName.WIDGET.equals(((PdfDictionary)annoto).get(PdfName.SUBTYPE))) {
899 ar.remove(idx);
900 --idx;
901 }
902 }
903 if (ar.isEmpty()) {
904 PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
905 pageDic.remove(PdfName.ANNOTS);
906 }
907 }
908 eliminateAcroformObjects();
909 }
910 }
911
912 void eliminateAcroformObjects() {
913 PdfObject acro = reader.getCatalog().get(PdfName.ACROFORM);
914 if (acro == null)
915 return;
916 PdfDictionary acrodic = (PdfDictionary)PdfReader.getPdfObject(acro);
917 reader.killXref(acrodic.get(PdfName.XFA));
918 acrodic.remove(PdfName.XFA);
919 PdfObject iFields = acrodic.get(PdfName.FIELDS);
920 if (iFields != null) {
921 PdfDictionary kids = new PdfDictionary();
922 kids.put(PdfName.KIDS, iFields);
923 sweepKids(kids);
924 PdfReader.killIndirect(iFields);
925 acrodic.put(PdfName.FIELDS, new PdfArray());
926 }
927 // PdfReader.killIndirect(acro);
928 // reader.getCatalog().remove(PdfName.ACROFORM);
929 }
930
931 void sweepKids(PdfObject obj) {
932 PdfObject oo = PdfReader.killIndirect(obj);
933 if (oo == null || !oo.isDictionary())
934 return;
935 PdfDictionary dic = (PdfDictionary)oo;
936 PdfArray kids = (PdfArray)PdfReader.killIndirect(dic.get(PdfName.KIDS));
937 if (kids == null)
938 return;
939 ArrayList ar = kids.getArrayList();
940 for (int k = 0; k < ar.size(); ++k) {
941 sweepKids((PdfObject)ar.get(k));
942 }
943 }
944
945 private void flatFreeTextFields()
946 {
947 if (append)
948 throw new IllegalArgumentException("FreeText flattening is not supported in append mode.");
949
950 for (int page = 1; page <= reader.getNumberOfPages(); ++page)
951 {
952 PdfDictionary pageDic = reader.getPageN(page);
953 PdfArray annots = (PdfArray)PdfReader.getPdfObject(pageDic.get(PdfName.ANNOTS));
954 if (annots == null)
955 continue;
956 ArrayList ar = annots.getArrayList();
957 for (int idx = 0; idx < ar.size(); ++idx)
958 {
959 PdfObject annoto = PdfReader.getPdfObject((PdfObject)ar.get(idx));
960 if ((annoto instanceof PdfIndirectReference) && !annoto.isIndirect())
961 continue;
962
963 PdfDictionary annDic = (PdfDictionary)annoto;
964 if (!((PdfName)annDic.get(PdfName.SUBTYPE)).equals(PdfName.FREETEXT))
965 continue;
966 PdfNumber ff = (PdfNumber)PdfReader.getPdfObject(annDic.get(PdfName.F));
967 int flags = (ff != null) ? ff.intValue() : 0;
968
969 if ( (flags & PdfFormField.FLAGS_PRINT) != 0 && (flags & PdfFormField.FLAGS_HIDDEN) == 0)
970 {
971 PdfObject obj1 = annDic.get(PdfName.AP);
972 if (obj1 == null)
973 continue;
974 PdfDictionary appDic = (obj1 instanceof PdfIndirectReference) ?
975 (PdfDictionary) PdfReader.getPdfObject(obj1) : (PdfDictionary) obj1;
976 PdfObject obj = appDic.get(PdfName.N);
977 PdfAppearance app = null;
978 PdfObject objReal = PdfReader.getPdfObject(obj);
979
980 if (obj instanceof PdfIndirectReference && !obj.isIndirect())
981 app = new PdfAppearance((PdfIndirectReference)obj);
982 else if (objReal instanceof PdfStream)
983 {
984 ((PdfDictionary)objReal).put(PdfName.SUBTYPE, PdfName.FORM);
985 app = new PdfAppearance((PdfIndirectReference)obj);
986 }
987 else
988 {
989 if (objReal.isDictionary())
990 {
991 PdfName as_p = (PdfName)PdfReader.getPdfObject(appDic.get(PdfName.AS));
992 if (as_p != null)
993 {
994 PdfIndirectReference iref = (PdfIndirectReference)((PdfDictionary)objReal).get(as_p);
995 if (iref != null)
996 {
997 app = new PdfAppearance(iref);
998 if (iref.isIndirect())
999 {
1000 objReal = PdfReader.getPdfObject(iref);
1001 ((PdfDictionary)objReal).put(PdfName.SUBTYPE, PdfName.FORM);
1002 }
1003 }
1004 }
1005 }
1006 }
1007 if (app != null)
1008 {
1009 Rectangle box = PdfReader.getNormalizedRectangle((PdfArray)PdfReader.getPdfObject(annDic.get(PdfName.RECT)));
1010 PdfContentByte cb = getOverContent(page);
1011 cb.setLiteral("Q ");
1012 cb.addTemplate(app, box.getLeft(), box.getBottom());
1013 cb.setLiteral("q ");
1014 }
1015 }
1016 }
1017 for (int idx = 0; idx < ar.size(); ++idx)
1018 {
1019 PdfObject annoto = PdfReader.getPdfObject((PdfObject)ar.get(idx));
1020 if (annoto != null && annoto.isDictionary())
1021 {
1022 PdfDictionary annot = (PdfDictionary)annoto;
1023 if (PdfName.FREETEXT.equals(annot.get(PdfName.SUBTYPE)))
1024 {
1025 ar.remove(idx);
1026 --idx;
1027 }
1028 }
1029 }
1030 if (ar.isEmpty())
1031 {
1032 PdfReader.killIndirect(pageDic.get(PdfName.ANNOTS));
1033 pageDic.remove(PdfName.ANNOTS);
1034 }
1035 }
1036 }
1037
1038 /**
1039 * @see com.lowagie.text.pdf.PdfWriter#getPageReference(int)
1040 */
1041 public PdfIndirectReference getPageReference(int page) {
1042 PdfIndirectReference ref = reader.getPageOrigRef(page);
1043 if (ref == null)
1044 throw new IllegalArgumentException("Invalid page number " + page);
1045 return ref;
1046 }
1047
1048 /**
1049 * @see com.lowagie.text.pdf.PdfWriter#addAnnotation(com.lowagie.text.pdf.PdfAnnotation)
1050 */
1051 public void addAnnotation(PdfAnnotation annot) {
1052 throw new RuntimeException("Unsupported in this context. Use PdfStamper.addAnnotation()");
1053 }
1054
1055 void addDocumentField(PdfIndirectReference ref) {
1056 PdfDictionary catalog = reader.getCatalog();
1057 PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), catalog);
1058 if (acroForm == null) {
1059 acroForm = new PdfDictionary();
1060 catalog.put(PdfName.ACROFORM, acroForm);
1061 markUsed(catalog);
1062 }
1063 PdfArray fields = (PdfArray)PdfReader.getPdfObject(acroForm.get(PdfName.FIELDS), acroForm);
1064 if (fields == null) {
1065 fields = new PdfArray();
1066 acroForm.put(PdfName.FIELDS, fields);
1067 markUsed(acroForm);
1068 }
1069 if (!acroForm.contains(PdfName.DA)) {
1070 acroForm.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
1071 markUsed(acroForm);
1072 }
1073 fields.add(ref);
1074 markUsed(fields);
1075 }
1076
1077 void addFieldResources() throws IOException {
1078 if (fieldTemplates.isEmpty())
1079 return;
1080 PdfDictionary catalog = reader.getCatalog();
1081 PdfDictionary acroForm = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.ACROFORM), catalog);
1082 if (acroForm == null) {
1083 acroForm = new PdfDictionary();
1084 catalog.put(PdfName.ACROFORM, acroForm);
1085 markUsed(catalog);
1086 }
1087 PdfDictionary dr = (PdfDictionary)PdfReader.getPdfObject(acroForm.get(PdfName.DR), acroForm);
1088 if (dr == null) {
1089 dr = new PdfDictionary();
1090 acroForm.put(PdfName.DR, dr);
1091 markUsed(acroForm);
1092 }
1093 markUsed(dr);
1094 for (Iterator it = fieldTemplates.keySet().iterator(); it.hasNext();) {
1095 PdfTemplate template = (PdfTemplate)it.next();
1096 PdfFormField.mergeResources(dr, (PdfDictionary)template.getResources(), this);
1097 }
1098 if (dr.get(PdfName.ENCODING) == null)
1099 dr.put(PdfName.ENCODING, PdfName.WIN_ANSI_ENCODING);
1100 PdfDictionary fonts = (PdfDictionary)PdfReader.getPdfObject(dr.get(PdfName.FONT));
1101 if (fonts == null) {
1102 fonts = new PdfDictionary();
1103 dr.put(PdfName.FONT, fonts);
1104 }
1105 if (!fonts.contains(PdfName.HELV)) {
1106 PdfDictionary dic = new PdfDictionary(PdfName.FONT);
1107 dic.put(PdfName.BASEFONT, PdfName.HELVETICA);
1108 dic.put(PdfName.ENCODING, PdfName.WIN_ANSI_ENCODING);
1109 dic.put(PdfName.NAME, PdfName.HELV);
1110 dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
1111 fonts.put(PdfName.HELV, addToBody(dic).getIndirectReference());
1112 }
1113 if (!fonts.contains(PdfName.ZADB)) {
1114 PdfDictionary dic = new PdfDictionary(PdfName.FONT);
1115 dic.put(PdfName.BASEFONT, PdfName.ZAPFDINGBATS);
1116 dic.put(PdfName.NAME, PdfName.ZADB);
1117 dic.put(PdfName.SUBTYPE, PdfName.TYPE1);
1118 fonts.put(PdfName.ZADB, addToBody(dic).getIndirectReference());
1119 }
1120 if (acroForm.get(PdfName.DA) == null) {
1121 acroForm.put(PdfName.DA, new PdfString("/Helv 0 Tf 0 g "));
1122 markUsed(acroForm);
1123 }
1124 }
1125
1126 void expandFields(PdfFormField field, ArrayList allAnnots) {
1127 allAnnots.add(field);
1128 ArrayList kids = field.getKids();
1129 if (kids != null) {
1130 for (int k = 0; k < kids.size(); ++k)
1131 expandFields((PdfFormField)kids.get(k), allAnnots);
1132 }
1133 }
1134
1135 void addAnnotation(PdfAnnotation annot, PdfDictionary pageN) {
1136 try {
1137 ArrayList allAnnots = new ArrayList();
1138 if (annot.isForm()) {
1139 fieldsAdded = true;
1140 getAcroFields();
1141 PdfFormField field = (PdfFormField)annot;
1142 if (field.getParent() != null)
1143 return;
1144 expandFields(field, allAnnots);
1145 }
1146 else
1147 allAnnots.add(annot);
1148 for (int k = 0; k < allAnnots.size(); ++k) {
1149 annot = (PdfAnnotation)allAnnots.get(k);
1150 if (annot.getPlaceInPage() > 0)
1151 pageN = reader.getPageN(annot.getPlaceInPage());
1152 if (annot.isForm()) {
1153 if (!annot.isUsed()) {
1154 HashMap templates = annot.getTemplates();
1155 if (templates != null)
1156 fieldTemplates.putAll(templates);
1157 }
1158 PdfFormField field = (PdfFormField)annot;
1159 if (field.getParent() == null)
1160 addDocumentField(field.getIndirectReference());
1161 }
1162 if (annot.isAnnotation()) {
1163 PdfObject pdfobj = PdfReader.getPdfObject(pageN.get(PdfName.ANNOTS), pageN);
1164 PdfArray annots = null;
1165 if (pdfobj == null || !pdfobj.isArray()) {
1166 annots = new PdfArray();
1167 pageN.put(PdfName.ANNOTS, annots);
1168 markUsed(pageN);
1169 }
1170 else
1171 annots = (PdfArray)pdfobj;
1172 annots.add(annot.getIndirectReference());
1173 markUsed(annots);
1174 if (!annot.isUsed()) {
1175 PdfRectangle rect = (PdfRectangle)annot.get(PdfName.RECT);
1176 if (rect != null && (rect.left() != 0 || rect.right() != 0 || rect.top() != 0 || rect.bottom() != 0)) {
1177 int rotation = reader.getPageRotation(pageN);
1178 Rectangle pageSize = reader.getPageSizeWithRotation(pageN);
1179 switch (rotation) {
1180 case 90:
1181 annot.put(PdfName.RECT, new PdfRectangle(
1182 pageSize.getTop() - rect.bottom(),
1183 rect.left(),
1184 pageSize.getTop() - rect.top(),
1185 rect.right()));
1186 break;
1187 case 180:
1188 annot.put(PdfName.RECT, new PdfRectangle(
1189 pageSize.getRight() - rect.left(),
1190 pageSize.getTop() - rect.bottom(),
1191 pageSize.getRight() - rect.right(),
1192 pageSize.getTop() - rect.top()));
1193 break;
1194 case 270:
1195 annot.put(PdfName.RECT, new PdfRectangle(
1196 rect.bottom(),
1197 pageSize.getRight() - rect.left(),
1198 rect.top(),
1199 pageSize.getRight() - rect.right()));
1200 break;
1201 }
1202 }
1203 }
1204 }
1205 if (!annot.isUsed()) {
1206 annot.setUsed();
1207 addToBody(annot, annot.getIndirectReference());
1208 }
1209 }
1210 }
1211 catch (IOException e) {
1212 throw new ExceptionConverter(e);
1213 }
1214 }
1215
1216 void addAnnotation(PdfAnnotation annot, int page) {
1217 addAnnotation(annot, reader.getPageN(page));
1218 }
1219
1220 private void outlineTravel(PRIndirectReference outline) {
1221 while (outline != null) {
1222 PdfDictionary outlineR = (PdfDictionary)PdfReader.getPdfObjectRelease(outline);
1223 PRIndirectReference first = (PRIndirectReference)outlineR.get(PdfName.FIRST);
1224 if (first != null) {
1225 outlineTravel(first);
1226 }
1227 PdfReader.killIndirect(outlineR.get(PdfName.DEST));
1228 PdfReader.killIndirect(outlineR.get(PdfName.A));
1229 PdfReader.killIndirect(outline);
1230 outline = (PRIndirectReference)outlineR.get(PdfName.NEXT);
1231 }
1232 }
1233
1234 void deleteOutlines() {
1235 PdfDictionary catalog = reader.getCatalog();
1236 PRIndirectReference outlines = (PRIndirectReference)catalog.get(PdfName.OUTLINES);
1237 if (outlines == null)
1238 return;
1239 outlineTravel(outlines);
1240 PdfReader.killIndirect(outlines);
1241 catalog.remove(PdfName.OUTLINES);
1242 markUsed(catalog);
1243 }
1244
1245 void setJavaScript() throws IOException {
1246 HashMap djs = pdf.getDocumentLevelJS();
1247 if (djs.isEmpty())
1248 return;
1249 PdfDictionary catalog = reader.getCatalog();
1250 PdfDictionary names = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.NAMES), catalog);
1251 if (names == null) {
1252 names = new PdfDictionary();
1253 catalog.put(PdfName.NAMES, names);
1254 markUsed(catalog);
1255 }
1256 markUsed(names);
1257 PdfDictionary tree = PdfNameTree.writeTree(djs, this);
1258 names.put(PdfName.JAVASCRIPT, addToBody(tree).getIndirectReference());
1259 }
1260
1261 void addFileAttachments() throws IOException {
1262 HashMap fs = pdf.getDocumentFileAttachment();
1263 if (fs.isEmpty())
1264 return;
1265 PdfDictionary catalog = reader.getCatalog();
1266 PdfDictionary names = (PdfDictionary)PdfReader.getPdfObject(catalog.get(PdfName.NAMES), catalog);
1267 if (names == null) {
1268 names = new PdfDictionary();
1269 catalog.put(PdfName.NAMES, names);
1270 markUsed(catalog);
1271 }
1272 markUsed(names);
1273 HashMap old = PdfNameTree.readTree((PdfDictionary)PdfReader.getPdfObjectRelease(names.get(PdfName.EMBEDDEDFILES)));
1274 for (Iterator it = fs.entrySet().iterator(); it.hasNext();) {
1275 Map.Entry entry = (Map.Entry) it.next();
1276 String name = (String) entry.getKey();
1277 int k = 0;
1278 String nn = name;
1279 while (old.containsKey(nn)) {
1280 ++k;
1281 nn += " " + k;
1282 }
1283 old.put(nn, entry.getValue());
1284 }
1285 PdfDictionary tree = PdfNameTree.writeTree(old, this);
1286 names.put(PdfName.EMBEDDEDFILES, addToBody(tree).getIndirectReference());
1287 }
1288
1289 /**
1290 * Adds or replaces the Collection Dictionary in the Catalog.
1291 * @param collection the new collection dictionary.
1292 */
1293 void makePackage( PdfCollection collection ) {
1294 PdfDictionary catalog = reader.getCatalog();
1295 catalog.put( PdfName.COLLECTION, collection );
1296 }
1297
1298 void setOutlines() throws IOException {
1299 if (newBookmarks == null)
1300 return;
1301 deleteOutlines();
1302 if (newBookmarks.isEmpty())
1303 return;
1304 PdfDictionary catalog = reader.getCatalog();
1305 boolean namedAsNames = (catalog.get(PdfName.DESTS) != null);
1306 writeOutlines(catalog, namedAsNames);
1307 markUsed(catalog);
1308 }
1309
1310 /**
1311 * Sets the viewer preferences.
1312 * @param preferences the viewer preferences
1313 * @see PdfWriter#setViewerPreferences(int)
1314 */
1315 public void setViewerPreferences(int preferences) {
1316 useVp = true;
1317 this.viewerPreferences.setViewerPreferences(preferences);
1318 }
1319
1320 /** Adds a viewer preference
1321 * @param key a key for a viewer preference
1322 * @param value the value for the viewer preference
1323 * @see PdfViewerPreferences#addViewerPreference
1324 */
1325 public void addViewerPreference(PdfName key, PdfObject value) {
1326 useVp = true;
1327 this.viewerPreferences.addViewerPreference(key, value);
1328 }
1329
1330 /**
1331 * Set the signature flags.
1332 * @param f the flags. This flags are ORed with current ones
1333 */
1334 public void setSigFlags(int f) {
1335 sigFlags |= f;
1336 }
1337
1338 /** Always throws an <code>UnsupportedOperationException</code>.
1339 * @param actionType ignore
1340 * @param action ignore
1341 * @throws PdfException ignore
1342 * @see PdfStamper#setPageAction(PdfName, PdfAction, int)
1343 */
1344 public void setPageAction(PdfName actionType, PdfAction action) throws PdfException {
1345 throw new UnsupportedOperationException("Use setPageAction(PdfName actionType, PdfAction action, int page)");
1346 }
1347
1348 /**
1349 * Sets the open and close page additional action.
1350 * @param actionType the action type. It can be <CODE>PdfWriter.PAGE_OPEN</CODE>
1351 * or <CODE>PdfWriter.PAGE_CLOSE</CODE>
1352 * @param action the action to perform
1353 * @param page the page where the action will be applied. The first page is 1
1354 * @throws PdfException if the action type is invalid
1355 */
1356 void setPageAction(PdfName actionType, PdfAction action, int page) throws PdfException {
1357 if (!actionType.equals(PAGE_OPEN) && !actionType.equals(PAGE_CLOSE))
1358 throw new PdfException("Invalid page additional action type: " + actionType.toString());
1359 PdfDictionary pg = reader.getPageN(page);
1360 PdfDictionary aa = (PdfDictionary)PdfReader.getPdfObject(pg.get(PdfName.AA), pg);
1361 if (aa == null) {
1362 aa = new PdfDictionary();
1363 pg.put(PdfName.AA, aa);
1364 markUsed(pg);
1365 }
1366 aa.put(actionType, action);
1367 markUsed(aa);
1368 }
1369
1370 /**
1371 * Always throws an <code>UnsupportedOperationException</code>.
1372 * @param seconds ignore
1373 */
1374 public void setDuration(int seconds) {
1375 throw new UnsupportedOperationException("Use setPageAction(PdfName actionType, PdfAction action, int page)");
1376 }
1377
1378 /**
1379 * Always throws an <code>UnsupportedOperationException</code>.
1380 * @param transition ignore
1381 */
1382 public void setTransition(PdfTransition transition) {
1383 throw new UnsupportedOperationException("Use setPageAction(PdfName actionType, PdfAction action, int page)");
1384 }
1385
1386 /**
1387 * Sets the display duration for the page (for presentations)
1388 * @param seconds the number of seconds to display the page. A negative value removes the entry
1389 * @param page the page where the duration will be applied. The first page is 1
1390 */
1391 void setDuration(int seconds, int page) {
1392 PdfDictionary pg = reader.getPageN(page);
1393 if (seconds < 0)
1394 pg.remove(PdfName.DUR);
1395 else
1396 pg.put(PdfName.DUR, new PdfNumber(seconds));
1397 markUsed(pg);
1398 }
1399
1400 /**
1401 * Sets the transition for the page
1402 * @param transition the transition object. A <code>null</code> removes the transition
1403 * @param page the page where the transition will be applied. The first page is 1
1404 */
1405 void setTransition(PdfTransition transition, int page) {
1406 PdfDictionary pg = reader.getPageN(page);
1407 if (transition == null)
1408 pg.remove(PdfName.TRANS);
1409 else
1410 pg.put(PdfName.TRANS, transition.getTransitionDictionary());
1411 markUsed(pg);
1412 }
1413
1414 protected void markUsed(PdfObject obj) {
1415 if (append && obj != null) {
1416 PRIndirectReference ref = null;
1417 if (obj.type() == PdfObject.INDIRECT)
1418 ref = (PRIndirectReference)obj;
1419 else
1420 ref = obj.getIndRef();
1421 if (ref != null)
1422 marked.put(ref.getNumber(), 1);
1423 }
1424 }
1425
1426 protected void markUsed(int num) {
1427 if (append)
1428 marked.put(num, 1);
1429 }
1430
1431 /**
1432 * Getter for property append.
1433 * @return Value of property append.
1434 */
1435 boolean isAppend() {
1436 return append;
1437 }
1438
1439 /** Additional-actions defining the actions to be taken in
1440 * response to various trigger events affecting the document
1441 * as a whole. The actions types allowed are: <CODE>DOCUMENT_CLOSE</CODE>,
1442 * <CODE>WILL_SAVE</CODE>, <CODE>DID_SAVE</CODE>, <CODE>WILL_PRINT</CODE>
1443 * and <CODE>DID_PRINT</CODE>.
1444 *
1445 * @param actionType the action type
1446 * @param action the action to execute in response to the trigger
1447 * @throws PdfException on invalid action type
1448 */
1449 public void setAdditionalAction(PdfName actionType, PdfAction action) throws PdfException {
1450 if (!(actionType.equals(DOCUMENT_CLOSE) ||
1451 actionType.equals(WILL_SAVE) ||
1452 actionType.equals(DID_SAVE) ||
1453 actionType.equals(WILL_PRINT) ||
1454 actionType.equals(DID_PRINT))) {
1455 throw new PdfException("Invalid additional action type: " + actionType.toString());
1456 }
1457 PdfDictionary aa = (PdfDictionary)PdfReader.getPdfObject(reader.getCatalog().get(PdfName.AA));
1458 if (aa == null) {
1459 if (action == null)
1460 return;
1461 aa = new PdfDictionary();
1462 reader.getCatalog().put(PdfName.AA, aa);
1463 }
1464 markUsed(aa);
1465 if (action == null)
1466 aa.remove(actionType);
1467 else
1468 aa.put(actionType, action);
1469 }
1470
1471 /**
1472 * @see com.lowagie.text.pdf.PdfWriter#setOpenAction(com.lowagie.text.pdf.PdfAction)
1473 */
1474 public void setOpenAction(PdfAction action) {
1475 openAction = action;
1476 }
1477
1478 /**
1479 * @see com.lowagie.text.pdf.PdfWriter#setOpenAction(java.lang.String)
1480 */
1481 public void setOpenAction(String name) {
1482 throw new UnsupportedOperationException("Open actions by name are not supported.");
1483 }
1484
1485 /**
1486 * @see com.lowagie.text.pdf.PdfWriter#setThumbnail(com.lowagie.text.Image)
1487 */
1488 public void setThumbnail(com.lowagie.text.Image image) {
1489 throw new UnsupportedOperationException("Use PdfStamper.setThumbnail().");
1490 }
1491
1492 void setThumbnail(Image image, int page) throws PdfException, DocumentException {
1493 PdfIndirectReference thumb = getImageReference(addDirectImageSimple(image));
1494 reader.resetReleasePage();
1495 PdfDictionary dic = reader.getPageN(page);
1496 dic.put(PdfName.THUMB, thumb);
1497 reader.resetReleasePage();
1498 }
1499
1500 public PdfContentByte getDirectContentUnder() {
1501 throw new UnsupportedOperationException("Use PdfStamper.getUnderContent() or PdfStamper.getOverContent()");
1502 }
1503
1504 public PdfContentByte getDirectContent() {
1505 throw new UnsupportedOperationException("Use PdfStamper.getUnderContent() or PdfStamper.getOverContent()");
1506 }
1507
1508 /**
1509 * Reads the OCProperties dictionary from the catalog of the existing document
1510 * and fills the documentOCG, documentOCGorder and OCGRadioGroup variables in PdfWriter.
1511 * Note that the original OCProperties of the existing document can contain more information.
1512 * @since 2.1.2
1513 */
1514 protected void readOCProperties() {
1515 if (!documentOCG.isEmpty()) {
1516 return;
1517 }
1518 PdfDictionary dict = reader.getCatalog().getAsDict(PdfName.OCPROPERTIES);
1519 if (dict == null) {
1520 return;
1521 }
1522 PdfArray ocgs = dict.getAsArray(PdfName.OCGS);
1523 PdfIndirectReference ref;
1524 PdfLayer layer;
1525 HashMap ocgmap = new HashMap();
1526 for (Iterator i = ocgs.listIterator(); i.hasNext(); ) {
1527 ref = (PdfIndirectReference)i.next();
1528 layer = new PdfLayer(null);
1529 layer.setRef(ref);
1530 layer.setOnPanel(false);
1531 layer.merge((PdfDictionary)PdfReader.getPdfObject(ref));
1532 ocgmap.put(ref.toString(), layer);
1533 }
1534 PdfDictionary d = dict.getAsDict(PdfName.D);
1535 PdfArray off = d.getAsArray(PdfName.OFF);
1536 if (off != null) {
1537 for (Iterator i = off.listIterator(); i.hasNext(); ) {
1538 ref = (PdfIndirectReference)i.next();
1539 layer = (PdfLayer)ocgmap.get(ref.toString());
1540 layer.setOn(false);
1541 }
1542 }
1543 PdfArray order = d.getAsArray(PdfName.ORDER);
1544 if (order != null) {
1545 addOrder(null, order, ocgmap);
1546 }
1547 documentOCG.addAll(ocgmap.values());
1548 OCGRadioGroup = d.getAsArray(PdfName.RBGROUPS);
1549 OCGLocked = d.getAsArray(PdfName.LOCKED);
1550 }
1551
1552 /**
1553 * Recursive method to reconstruct the documentOCGorder variable in the writer.
1554 * @param parent a parent PdfLayer (can be null)
1555 * @param arr an array possibly containing children for the parent PdfLayer
1556 * @param ocgmap a HashMap with indirect reference Strings as keys and PdfLayer objects as values.
1557 * @since 2.1.2
1558 */
1559 private void addOrder(PdfLayer parent, PdfArray arr, Map ocgmap) {
1560 PdfObject obj;
1561 PdfLayer layer;
1562 for (int i = 0; i < arr.size(); i++) {
1563 obj = arr.getPdfObject(i);
1564 if (obj.isIndirect()) {
1565 layer = (PdfLayer)ocgmap.get(obj.toString());
1566 layer.setOnPanel(true);
1567 registerLayer(layer);
1568 if (parent != null) {
1569 parent.addChild(layer);
1570 }
1571 if (arr.size() > i + 1 && arr.getPdfObject(i + 1).isArray()) {
1572 i++;
1573 addOrder(layer, (PdfArray)arr.getPdfObject(i), ocgmap);
1574 }
1575 }
1576 else if (obj.isArray()) {
1577 ArrayList sub = ((PdfArray)obj).getArrayList();
1578 if (sub.isEmpty()) return;
1579 obj = (PdfObject)sub.get(0);
1580 if (obj.isString()) {
1581 layer = new PdfLayer(sub.get(0).toString());
1582 layer.setOnPanel(true);
1583 registerLayer(layer);
1584 if (parent != null) {
1585 parent.addChild(layer);
1586 }
1587 PdfArray array = new PdfArray();
1588 for (Iterator j = sub.iterator(); j.hasNext(); ) {
1589 array.add((PdfObject)j.next());
1590 }
1591 addOrder(layer, array, ocgmap);
1592 }
1593 else {
1594 addOrder(parent, (PdfArray)obj, ocgmap);
1595 }
1596 }
1597 }
1598 }
1599
1600 /**
1601 * Gets the PdfLayer objects in an existing document as a Map
1602 * with the names/titles of the layers as keys.
1603 * @return a Map with all the PdfLayers in the document (and the name/title of the layer as key)
1604 * @since 2.1.2
1605 */
1606 public Map getPdfLayers() {
1607 if (documentOCG.isEmpty()) {
1608 readOCProperties();
1609 }
1610 HashMap map = new HashMap();
1611 PdfLayer layer;
1612 String key;
1613 for (Iterator i = documentOCG.iterator(); i.hasNext(); ) {
1614 layer = (PdfLayer)i.next();
1615 if (layer.getTitle() == null) {
1616 key = layer.getAsString(PdfName.NAME).toString();
1617 }
1618 else {
1619 key = layer.getTitle();
1620 }
1621 if (map.containsKey(key)) {
1622 int seq = 2;
1623 String tmp = key + "(" + seq + ")";
1624 while (map.containsKey(tmp)) {
1625 seq++;
1626 tmp = key + "(" + seq + ")";
1627 }
1628 key = tmp;
1629 }
1630 map.put(key, layer);
1631 }
1632 return map;
1633 }
1634
1635 static class PageStamp {
1636
1637 PdfDictionary pageN;
1638 StampContent under;
1639 StampContent over;
1640 PageResources pageResources;
1641 int replacePoint = 0;
1642
1643 PageStamp(PdfStamperImp stamper, PdfReader reader, PdfDictionary pageN) {
1644 this.pageN = pageN;
1645 pageResources = new PageResources();
1646 PdfDictionary resources = (PdfDictionary)PdfReader.getPdfObject(pageN.get(PdfName.RESOURCES));
1647 pageResources.setOriginalResources(resources, stamper.namePtr);
1648 }
1649 }
1650 }