KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > lowagie > text > pdf > PdfReader


1 /*
2  * $Id: PdfReader.java 2764 2007-05-18 21:17:56Z xlv $
3  * $Name$
4  *
5  * Copyright 2001, 2002 Paulo Soares
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.io.ByteArrayInputStream JavaDoc;
54 import java.io.ByteArrayOutputStream JavaDoc;
55 import java.io.DataInputStream JavaDoc;
56 import java.io.IOException JavaDoc;
57 import java.io.InputStream JavaDoc;
58 import java.net.URL JavaDoc;
59 import java.util.ArrayList JavaDoc;
60 import java.util.Arrays JavaDoc;
61 import java.util.Collections JavaDoc;
62 import java.util.HashMap JavaDoc;
63 import java.util.Iterator JavaDoc;
64 import java.util.List JavaDoc;
65 import java.util.Map JavaDoc;
66 import java.util.Set JavaDoc;
67 import java.util.zip.InflaterInputStream JavaDoc;
68 import java.util.Stack JavaDoc;
69 import java.security.Key JavaDoc;
70 import java.security.MessageDigest JavaDoc;
71 import java.security.cert.Certificate JavaDoc;
72
73 import com.lowagie.text.ExceptionConverter;
74 import com.lowagie.text.PageSize;
75 import com.lowagie.text.Rectangle;
76 import com.lowagie.text.pdf.interfaces.PdfViewerPreferences;
77 import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
78
79 import org.bouncycastle.cms.CMSEnvelopedData;
80 import org.bouncycastle.cms.RecipientInformation;
81
82 /** Reads a PDF document.
83  * @author Paulo Soares (psoares@consiste.pt)
84  * @author Kazuya Ujihara
85  */

86 public class PdfReader implements PdfViewerPreferences {
87
88     static final PdfName pageInhCandidates[] = {
89         PdfName.MEDIABOX, PdfName.ROTATE, PdfName.RESOURCES, PdfName.CROPBOX
90     };
91    
92     static final byte endstream[] = PdfEncodings.convertToBytes("endstream", null);
93     static final byte endobj[] = PdfEncodings.convertToBytes("endobj", null);
94     protected PRTokeniser tokens;
95     // Each xref pair is a position
96
// type 0 -> -1, 0
97
// type 1 -> offset, 0
98
// type 2 -> index, obj num
99
protected int xref[];
100     protected HashMap JavaDoc objStmMark;
101     protected IntHashtable objStmToOffset;
102     protected boolean newXrefType;
103     private ArrayList JavaDoc xrefObj;
104     PdfDictionary rootPages;
105     protected PdfDictionary trailer;
106     protected PdfDictionary catalog;
107     protected PageRefs pageRefs;
108     protected PRAcroForm acroForm = null;
109     protected boolean acroFormParsed = false;
110     protected boolean encrypted = false;
111     protected boolean rebuilt = false;
112     protected int freeXref;
113     protected boolean tampered = false;
114     protected int lastXref;
115     protected int eofPos;
116     protected char pdfVersion;
117     protected PdfEncryption decrypt;
118     protected byte password[] = null; //added by ujihara for decryption
119
protected Key JavaDoc certificateKey = null; //added by Aiken Sam for certificate decryption
120
protected Certificate JavaDoc certificate = null; //added by Aiken Sam for certificate decryption
121
protected String JavaDoc certificateKeyProvider = null; //added by Aiken Sam for certificate decryption
122
private boolean ownerPasswordUsed;
123     protected ArrayList JavaDoc strings = new ArrayList JavaDoc();
124     protected boolean sharedStreams = true;
125     protected boolean consolidateNamedDestinations = false;
126     protected int rValue;
127     protected int pValue;
128     private int objNum;
129     private int objGen;
130     private int fileLength;
131     private boolean hybridXref;
132     private int lastXrefPartial = -1;
133     private boolean partial;
134     private PRIndirectReference cryptoRef;
135     private PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
136
137     /**
138      * Holds value of property appendable.
139      */

140     private boolean appendable;
141
142     protected PdfReader() {
143     }
144
145     /** Reads and parses a PDF document.
146      * @param filename the file name of the document
147      * @throws IOException on error
148      */

149     public PdfReader(String JavaDoc filename) throws IOException JavaDoc {
150         this(filename, null);
151     }
152
153     /** Reads and parses a PDF document.
154      * @param filename the file name of the document
155      * @param ownerPassword the password to read the document
156      * @throws IOException on error
157      */

158     public PdfReader(String JavaDoc filename, byte ownerPassword[]) throws IOException JavaDoc {
159         password = ownerPassword;
160         tokens = new PRTokeniser(filename);
161         readPdf();
162     }
163
164     /** Reads and parses a PDF document.
165      * @param pdfIn the byte array with the document
166      * @throws IOException on error
167      */

168     public PdfReader(byte pdfIn[]) throws IOException JavaDoc {
169         this(pdfIn, null);
170     }
171
172     /** Reads and parses a PDF document.
173      * @param pdfIn the byte array with the document
174      * @param ownerPassword the password to read the document
175      * @throws IOException on error
176      */

177     public PdfReader(byte pdfIn[], byte ownerPassword[]) throws IOException JavaDoc {
178         password = ownerPassword;
179         tokens = new PRTokeniser(pdfIn);
180         readPdf();
181     }
182
183     /** Reads and parses a PDF document.
184      * @param filename the file name of the document
185      * @param certificate the certificate to read the document
186      * @param certificateKey the private key of the certificate
187      * @param certificateKeyProvider the security provider for certificateKey
188      * @throws IOException on error
189      */

190     public PdfReader(String JavaDoc filename, Certificate JavaDoc certificate, Key JavaDoc certificateKey, String JavaDoc certificateKeyProvider) throws IOException JavaDoc {
191         this.certificate = certificate;
192         this.certificateKey = certificateKey;
193         this.certificateKeyProvider = certificateKeyProvider;
194         tokens = new PRTokeniser(filename);
195         readPdf();
196     }
197
198     /** Reads and parses a PDF document.
199      * @param url the URL of the document
200      * @throws IOException on error
201      */

202     public PdfReader(URL JavaDoc url) throws IOException JavaDoc {
203         this(url, null);
204     }
205
206     /** Reads and parses a PDF document.
207      * @param url the URL of the document
208      * @param ownerPassword the password to read the document
209      * @throws IOException on error
210      */

211     public PdfReader(URL JavaDoc url, byte ownerPassword[]) throws IOException JavaDoc {
212         password = ownerPassword;
213         tokens = new PRTokeniser(new RandomAccessFileOrArray(url));
214         readPdf();
215     }
216
217     /**
218      * Reads and parses a PDF document.
219      * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
220      * end but is not closed
221      * @param ownerPassword the password to read the document
222      * @throws IOException on error
223      */

224     public PdfReader(InputStream JavaDoc is, byte ownerPassword[]) throws IOException JavaDoc {
225         password = ownerPassword;
226         tokens = new PRTokeniser(new RandomAccessFileOrArray(is));
227         readPdf();
228     }
229
230     /**
231      * Reads and parses a PDF document.
232      * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
233      * end but is not closed
234      * @throws IOException on error
235      */

236     public PdfReader(InputStream JavaDoc is) throws IOException JavaDoc {
237         this(is, null);
238     }
239
240     /**
241      * Reads and parses a pdf document. Contrary to the other constructors only the xref is read
242      * into memory. The reader is said to be working in "partial" mode as only parts of the pdf
243      * are read as needed. The pdf is left open but may be closed at any time with
244      * <CODE>PdfReader.close()</CODE>, reopen is automatic.
245      * @param raf the document location
246      * @param ownerPassword the password or <CODE>null</CODE> for no password
247      * @throws IOException on error
248      */

249     public PdfReader(RandomAccessFileOrArray raf, byte ownerPassword[]) throws IOException JavaDoc {
250         password = ownerPassword;
251         partial = true;
252         tokens = new PRTokeniser(raf);
253         readPdfPartial();
254     }
255
256     /** Creates an independent duplicate.
257      * @param reader the <CODE>PdfReader</CODE> to duplicate
258      */

259     public PdfReader(PdfReader reader) {
260         this.appendable = reader.appendable;
261         this.consolidateNamedDestinations = reader.consolidateNamedDestinations;
262         this.encrypted = reader.encrypted;
263         this.rebuilt = reader.rebuilt;
264         this.sharedStreams = reader.sharedStreams;
265         this.tampered = reader.tampered;
266         this.password = reader.password;
267         this.pdfVersion = reader.pdfVersion;
268         this.eofPos = reader.eofPos;
269         this.freeXref = reader.freeXref;
270         this.lastXref = reader.lastXref;
271         this.tokens = new PRTokeniser(reader.tokens.getSafeFile());
272         if (reader.decrypt != null)
273             this.decrypt = new PdfEncryption(reader.decrypt);
274         this.pValue = reader.pValue;
275         this.rValue = reader.rValue;
276         this.xrefObj = new ArrayList JavaDoc(reader.xrefObj);
277         for (int k = 0; k < reader.xrefObj.size(); ++k) {
278             this.xrefObj.set(k, duplicatePdfObject((PdfObject)reader.xrefObj.get(k), this));
279         }
280         this.pageRefs = new PageRefs(reader.pageRefs, this);
281         this.trailer = (PdfDictionary)duplicatePdfObject(reader.trailer, this);
282         this.catalog = (PdfDictionary)getPdfObject(trailer.get(PdfName.ROOT));
283         this.rootPages = (PdfDictionary)getPdfObject(catalog.get(PdfName.PAGES));
284         this.fileLength = reader.fileLength;
285         this.partial = reader.partial;
286         this.hybridXref = reader.hybridXref;
287         this.objStmToOffset = reader.objStmToOffset;
288         this.xref = reader.xref;
289         this.cryptoRef = (PRIndirectReference)duplicatePdfObject(reader.cryptoRef, this);
290         this.ownerPasswordUsed = reader.ownerPasswordUsed;
291     }
292
293     /** Gets a new file instance of the original PDF
294      * document.
295      * @return a new file instance of the original PDF document
296      */

297     public RandomAccessFileOrArray getSafeFile() {
298         return tokens.getSafeFile();
299     }
300
301     protected PdfReaderInstance getPdfReaderInstance(PdfWriter writer) {
302         return new PdfReaderInstance(this, writer);
303     }
304
305     /** Gets the number of pages in the document.
306      * @return the number of pages in the document
307      */

308     public int getNumberOfPages() {
309         return pageRefs.size();
310     }
311
312     /** Returns the document's catalog. This dictionary is not a copy,
313      * any changes will be reflected in the catalog.
314      * @return the document's catalog
315      */

316     public PdfDictionary getCatalog() {
317         return catalog;
318     }
319
320     /** Returns the document's acroform, if it has one.
321      * @return the document's acroform
322      */

323     public PRAcroForm getAcroForm() {
324         if (!acroFormParsed) {
325             acroFormParsed = true;
326             PdfObject form = catalog.get(PdfName.ACROFORM);
327             if (form != null) {
328                 try {
329                     acroForm = new PRAcroForm(this);
330                     acroForm.readAcroForm((PdfDictionary)getPdfObject(form));
331                 }
332                 catch (Exception JavaDoc e) {
333                     acroForm = null;
334                 }
335             }
336         }
337         return acroForm;
338     }
339     /**
340      * Gets the page rotation. This value can be 0, 90, 180 or 270.
341      * @param index the page number. The first page is 1
342      * @return the page rotation
343      */

344     public int getPageRotation(int index) {
345         return getPageRotation(pageRefs.getPageNRelease(index));
346     }
347
348     int getPageRotation(PdfDictionary page) {
349         PdfNumber rotate = (PdfNumber)getPdfObject(page.get(PdfName.ROTATE));
350         if (rotate == null)
351             return 0;
352         else {
353             int n = rotate.intValue();
354             n %= 360;
355             return n < 0 ? n + 360 : n;
356         }
357     }
358     /** Gets the page size, taking rotation into account. This
359      * is a <CODE>Rectangle</CODE> with the value of the /MediaBox and the /Rotate key.
360      * @param index the page number. The first page is 1
361      * @return a <CODE>Rectangle</CODE>
362      */

363     public Rectangle getPageSizeWithRotation(int index) {
364         return getPageSizeWithRotation(pageRefs.getPageNRelease(index));
365     }
366
367     /**
368      * Gets the rotated page from a page dictionary.
369      * @param page the page dictionary
370      * @return the rotated page
371      */

372     public Rectangle getPageSizeWithRotation(PdfDictionary page) {
373         Rectangle rect = getPageSize(page);
374         int rotation = getPageRotation(page);
375         while (rotation > 0) {
376             rect = rect.rotate();
377             rotation -= 90;
378         }
379         return rect;
380     }
381
382     /** Gets the page size without taking rotation into account. This
383      * is the value of the /MediaBox key.
384      * @param index the page number. The first page is 1
385      * @return the page size
386      */

387     public Rectangle getPageSize(int index) {
388         return getPageSize(pageRefs.getPageNRelease(index));
389     }
390
391     /**
392      * Gets the page from a page dictionary
393      * @param page the page dictionary
394      * @return the page
395      */

396     public Rectangle getPageSize(PdfDictionary page) {
397         PdfArray mediaBox = (PdfArray)getPdfObject(page.get(PdfName.MEDIABOX));
398         return getNormalizedRectangle(mediaBox);
399     }
400
401     /** Gets the crop box without taking rotation into account. This
402      * is the value of the /CropBox key. The crop box is the part
403      * of the document to be displayed or printed. It usually is the same
404      * as the media box but may be smaller. If the page doesn't have a crop
405      * box the page size will be returned.
406      * @param index the page number. The first page is 1
407      * @return the crop box
408      */

409     public Rectangle getCropBox(int index) {
410         PdfDictionary page = pageRefs.getPageNRelease(index);
411         PdfArray cropBox = (PdfArray)getPdfObjectRelease(page.get(PdfName.CROPBOX));
412         if (cropBox == null)
413             return getPageSize(page);
414         return getNormalizedRectangle(cropBox);
415     }
416
417     /** Gets the box size. Allowed names are: "crop", "trim", "art", "bleed" and "media".
418      * @param index the page number. The first page is 1
419      * @param boxName the box name
420      * @return the box rectangle or null
421      */

422     public Rectangle getBoxSize(int index, String JavaDoc boxName) {
423         PdfDictionary page = pageRefs.getPageNRelease(index);
424         PdfArray box = null;
425         if (boxName.equals("trim"))
426             box = (PdfArray)getPdfObjectRelease(page.get(PdfName.TRIMBOX));
427         else if (boxName.equals("art"))
428             box = (PdfArray)getPdfObjectRelease(page.get(PdfName.ARTBOX));
429         else if (boxName.equals("bleed"))
430             box = (PdfArray)getPdfObjectRelease(page.get(PdfName.BLEEDBOX));
431         else if (boxName.equals("crop"))
432             box = (PdfArray)getPdfObjectRelease(page.get(PdfName.CROPBOX));
433         else if (boxName.equals("media"))
434             box = (PdfArray)getPdfObjectRelease(page.get(PdfName.MEDIABOX));
435         if (box == null)
436             return null;
437         return getNormalizedRectangle(box);
438     }
439
440     /** Returns the content of the document information dictionary as a <CODE>HashMap</CODE>
441      * of <CODE>String</CODE>.
442      * @return content of the document information dictionary
443      */

444     public HashMap JavaDoc getInfo() {
445         HashMap JavaDoc map = new HashMap JavaDoc();
446         PdfDictionary info = (PdfDictionary)getPdfObject(trailer.get(PdfName.INFO));
447         if (info == null)
448             return map;
449         for (Iterator JavaDoc it = info.getKeys().iterator(); it.hasNext();) {
450             PdfName key = (PdfName)it.next();
451             PdfObject obj = getPdfObject(info.get(key));
452             if (obj == null)
453                 continue;
454             String JavaDoc value = obj.toString();
455             switch (obj.type()) {
456                 case PdfObject.STRING: {
457                     value = ((PdfString)obj).toUnicodeString();
458                     break;
459                 }
460                 case PdfObject.NAME: {
461                     value = PdfName.decodeName(value);
462                     break;
463                 }
464             }
465             map.put(PdfName.decodeName(key.toString()), value);
466         }
467         return map;
468     }
469
470     /** Normalizes a <CODE>Rectangle</CODE> so that llx and lly are smaller than urx and ury.
471      * @param box the original rectangle
472      * @return a normalized <CODE>Rectangle</CODE>
473      */

474     public static Rectangle getNormalizedRectangle(PdfArray box) {
475         ArrayList JavaDoc rect = box.getArrayList();
476         float llx = ((PdfNumber)rect.get(0)).floatValue();
477         float lly = ((PdfNumber)rect.get(1)).floatValue();
478         float urx = ((PdfNumber)rect.get(2)).floatValue();
479         float ury = ((PdfNumber)rect.get(3)).floatValue();
480         return new Rectangle(Math.min(llx, urx), Math.min(lly, ury),
481         Math.max(llx, urx), Math.max(lly, ury));
482     }
483
484     protected void readPdf() throws IOException JavaDoc {
485         try {
486             fileLength = tokens.getFile().length();
487             pdfVersion = tokens.checkPdfHeader();
488             try {
489                 readXref();
490             }
491             catch (Exception JavaDoc e) {
492                 try {
493                     rebuilt = true;
494                     rebuildXref();
495                     lastXref = -1;
496                 }
497                 catch (Exception JavaDoc ne) {
498                     throw new IOException JavaDoc("Rebuild failed: " + ne.getMessage() + "; Original message: " + e.getMessage());
499                 }
500             }
501             try {
502                 readDocObj();
503             }
504             catch (Exception JavaDoc ne) {
505                 if (rebuilt)
506                     throw new IOException JavaDoc(ne.getMessage());
507                 rebuilt = true;
508                 encrypted = false;
509                 rebuildXref();
510                 lastXref = -1;
511                 readDocObj();
512             }
513
514             strings.clear();
515             readPages();
516             eliminateSharedStreams();
517             removeUnusedObjects();
518         }
519         finally {
520             try {
521                 tokens.close();
522             }
523             catch (Exception JavaDoc e) {
524                 // empty on purpose
525
}
526         }
527     }
528
529     protected void readPdfPartial() throws IOException JavaDoc {
530         try {
531             fileLength = tokens.getFile().length();
532             pdfVersion = tokens.checkPdfHeader();
533             try {
534                 readXref();
535             }
536             catch (Exception JavaDoc e) {
537                 try {
538                     rebuilt = true;
539                     rebuildXref();
540                     lastXref = -1;
541                 }
542                 catch (Exception JavaDoc ne) {
543                     throw new IOException JavaDoc("Rebuild failed: " + ne.getMessage() + "; Original message: " + e.getMessage());
544                 }
545             }
546             readDocObjPartial();
547             readPages();
548         }
549         catch (IOException JavaDoc e) {
550             try{tokens.close();}catch(Exception JavaDoc ee){}
551             throw e;
552         }
553     }
554
555     private boolean equalsArray(byte ar1[], byte ar2[], int size) {
556         for (int k = 0; k < size; ++k) {
557             if (ar1[k] != ar2[k])
558                 return false;
559         }
560         return true;
561     }
562
563     /**
564      * @throws IOException
565      */

566     private void readDecryptedDocObj() throws IOException JavaDoc {
567         if (encrypted)
568             return;
569         PdfObject encDic = trailer.get(PdfName.ENCRYPT);
570         if (encDic == null || encDic.toString().equals("null"))
571             return;
572         byte[] encryptionKey = null;
573         encrypted = true;
574         PdfDictionary enc = (PdfDictionary)getPdfObject(encDic);
575
576         String JavaDoc s;
577         PdfObject o;
578
579         PdfArray documentIDs = (PdfArray)getPdfObject(trailer.get(PdfName.ID));
580         byte documentID[] = null;
581         if (documentIDs != null) {
582             o = (PdfObject)documentIDs.getArrayList().get(0);
583             strings.remove(o);
584             s = o.toString();
585             documentID = com.lowagie.text.DocWriter.getISOBytes(s);
586             if (documentIDs.size() > 1)
587                 strings.remove(documentIDs.getArrayList().get(1));
588         }
589
590         byte uValue[] = null;
591         byte oValue[] = null;
592         int cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
593         int lengthValue = 0;
594         
595         PdfObject filter = getPdfObjectRelease(enc.get(PdfName.FILTER));
596
597         if (filter.equals(PdfName.STANDARD))
598         {
599             s = enc.get(PdfName.U).toString();
600             strings.remove(enc.get(PdfName.U));
601             uValue = com.lowagie.text.DocWriter.getISOBytes(s);
602             s = enc.get(PdfName.O).toString();
603             strings.remove(enc.get(PdfName.O));
604             oValue = com.lowagie.text.DocWriter.getISOBytes(s);
605
606             o = enc.get(PdfName.R);
607             if (!o.isNumber()) throw new IOException JavaDoc("Illegal R value.");
608             rValue = ((PdfNumber)o).intValue();
609             if (rValue != 2 && rValue != 3 && rValue != 4)
610                 throw new IOException JavaDoc("Unknown encryption type (" + rValue + ")");
611
612             o = enc.get(PdfName.P);
613             if (!o.isNumber()) throw new IOException JavaDoc("Illegal P value.");
614             pValue = ((PdfNumber)o).intValue();
615    
616             if ( rValue == 3 ){
617                 o = enc.get(PdfName.LENGTH);
618                 if (!o.isNumber())
619                     throw new IOException JavaDoc("Illegal Length value.");
620                 lengthValue = ( (PdfNumber) o).intValue();
621                 if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
622                     throw new IOException JavaDoc("Illegal Length value.");
623                 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
624             } else if (rValue == 4) {
625                 PdfDictionary dic = (PdfDictionary)enc.get(PdfName.CF);
626                 if (dic == null)
627                     throw new IOException JavaDoc("/CF not found (encryption)");
628                 dic = (PdfDictionary)dic.get(PdfName.STDCF);
629                 if (dic == null)
630                     throw new IOException JavaDoc("/StdCF not found (encryption)");
631                 if (PdfName.V2.equals(dic.get(PdfName.CFM)))
632                     cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
633                 else if (PdfName.AESV2.equals(dic.get(PdfName.CFM)))
634                     cryptoMode = PdfWriter.ENCRYPTION_AES_128;
635                 else
636                     throw new IOException JavaDoc("No compatible encryption found");
637                 PdfObject em = enc.get(PdfName.ENCRYPTMETADATA);
638                 if (em != null && em.toString().equals("false"))
639                     cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
640             } else {
641                 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
642             }
643         } else if (filter.equals(PdfName.PUBSEC)) {
644             boolean foundRecipient = false;
645             byte[] envelopedData = null;
646             PdfArray recipients = null;
647
648             o = enc.get(PdfName.V);
649             if (!o.isNumber()) throw new IOException JavaDoc("Illegal V value.");
650             int vValue = ((PdfNumber)o).intValue();
651             if (vValue != 1 && vValue != 2 && vValue != 4)
652                 throw new IOException JavaDoc("Unknown encryption type V = " + rValue);
653
654             if ( vValue == 2 ){
655                 o = enc.get(PdfName.LENGTH);
656                 if (!o.isNumber())
657                     throw new IOException JavaDoc("Illegal Length value.");
658                 lengthValue = ( (PdfNumber) o).intValue();
659                 if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
660                     throw new IOException JavaDoc("Illegal Length value.");
661                 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
662                 recipients = (PdfArray)enc.get(PdfName.RECIPIENTS);
663             } else if (vValue == 4) {
664                 PdfDictionary dic = (PdfDictionary)enc.get(PdfName.CF);
665                 if (dic == null)
666                     throw new IOException JavaDoc("/CF not found (encryption)");
667                 dic = (PdfDictionary)dic.get(PdfName.DEFAULTCRYPTFILER);
668                 if (dic == null)
669                     throw new IOException JavaDoc("/DefaultCryptFilter not found (encryption)");
670                 if (PdfName.V2.equals(dic.get(PdfName.CFM)))
671                 {
672                     cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
673                     lengthValue = 128;
674                 }
675                 else if (PdfName.AESV2.equals(dic.get(PdfName.CFM)))
676                 {
677                     cryptoMode = PdfWriter.ENCRYPTION_AES_128;
678                     lengthValue = 128;
679                 }
680                 else
681                     throw new IOException JavaDoc("No compatible encryption found");
682                 PdfObject em = dic.get(PdfName.ENCRYPTMETADATA);
683                 if (em != null && em.toString().equals("false"))
684                     cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
685                 
686                 recipients = (PdfArray)dic.get(PdfName.RECIPIENTS);
687             } else {
688                 cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
689                 lengthValue = 40;
690                 recipients = (PdfArray)enc.get(PdfName.RECIPIENTS);
691             }
692
693             for (int i = 0; i<recipients.size(); i++)
694             {
695                 PdfObject recipient = (PdfObject)recipients.getArrayList().get(i);
696                 strings.remove(recipient);
697                 
698                 CMSEnvelopedData data = null;
699                 try {
700                     data = new CMSEnvelopedData(recipient.getBytes());
701                 
702                     Iterator JavaDoc recipientCertificatesIt =
703                         data.getRecipientInfos().getRecipients().iterator();
704             
705                     while (recipientCertificatesIt.hasNext()) {
706                         RecipientInformation recipientInfo =
707                             (RecipientInformation)recipientCertificatesIt.next();
708
709                         if (recipientInfo.getRID().match(certificate) && !foundRecipient) {
710     
711                          envelopedData = recipientInfo.getContent(certificateKey, certificateKeyProvider);
712                          foundRecipient = true;
713                         }
714                     }
715                 } catch (Exception JavaDoc f) {
716                     throw new ExceptionConverter(f);
717                 }
718             }
719             
720             if(!foundRecipient || envelopedData == null)
721             {
722                 throw new IOException JavaDoc("Bad certificate and key.");
723             }
724
725             MessageDigest JavaDoc md = null;
726
727             try {
728                 md = MessageDigest.getInstance("SHA-1");
729                 md.update(envelopedData, 0, 20);
730                 for (int i=0; i<recipients.size(); i++)
731                 {
732                   byte[] encodedRecipient = ((PdfObject)recipients.getArrayList().get(i)).getBytes();
733                   md.update(encodedRecipient);
734                 }
735                 if ((cryptoMode & PdfWriter.DO_NOT_ENCRYPT_METADATA) != 0)
736                     md.update(new byte[]{(byte)255, (byte)255, (byte)255, (byte)255});
737                 encryptionKey = md.digest();
738                 
739             } catch (Exception JavaDoc f) {
740                 throw new ExceptionConverter(f);
741             }
742         }
743
744
745         decrypt = new PdfEncryption();
746         decrypt.setCryptoMode(cryptoMode, lengthValue);
747
748         if (filter.equals(PdfName.STANDARD))
749         {
750             //check by owner password
751
decrypt.setupByOwnerPassword(documentID, password, uValue, oValue, pValue);
752             if (!equalsArray(uValue, decrypt.userKey, (rValue == 3 || rValue == 4) ? 16 : 32)) {
753                 //check by user password
754
decrypt.setupByUserPassword(documentID, password, oValue, pValue);
755                 if (!equalsArray(uValue, decrypt.userKey, (rValue == 3 || rValue == 4) ? 16 : 32)) {
756                     throw new IOException JavaDoc("Bad user password");
757                 }
758             }
759             else
760                 ownerPasswordUsed = true;
761         } else if (filter.equals(PdfName.PUBSEC)) {
762             decrypt.setupByEncryptionKey(encryptionKey, lengthValue);
763             ownerPasswordUsed = true;
764         }
765                  
766         for (int k = 0; k < strings.size(); ++k) {
767             PdfString str = (PdfString)strings.get(k);
768             str.decrypt(this);
769         }
770         
771         if (encDic.isIndirect()) {
772             cryptoRef = (PRIndirectReference)encDic;
773             xrefObj.set(cryptoRef.getNumber(), null);
774         }
775     }
776
777     /**
778      * @param obj
779      * @return a PdfObject
780      */

781     public static PdfObject getPdfObjectRelease(PdfObject obj) {
782         PdfObject obj2 = getPdfObject(obj);
783         releaseLastXrefPartial(obj);
784         return obj2;
785     }
786
787
788     /**
789      * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
790      * if needed.
791      * @param obj the <CODE>PdfObject</CODE> to read
792      * @return the resolved <CODE>PdfObject</CODE>
793      */

794     public static PdfObject getPdfObject(PdfObject obj) {
795         if (obj == null)
796             return null;
797         if (!obj.isIndirect())
798             return obj;
799         try {
800             PRIndirectReference ref = (PRIndirectReference)obj;
801             int idx = ref.getNumber();
802             boolean appendable = ref.getReader().appendable;
803             obj = ref.getReader().getPdfObject(idx);
804             if (obj == null) {
805                 return null;
806             }
807             else {
808                 if (appendable) {
809                     switch (obj.type()) {
810                         case PdfObject.NULL:
811                             obj = new PdfNull();
812                             break;
813                         case PdfObject.BOOLEAN:
814                             obj = new PdfBoolean(((PdfBoolean)obj).booleanValue());
815                             break;
816                         case PdfObject.NAME:
817                             obj = new PdfName(obj.getBytes());
818                             break;
819                     }
820                     obj.setIndRef(ref);
821                 }
822                 return obj;
823             }
824         }
825         catch (Exception JavaDoc e) {
826             throw new ExceptionConverter(e);
827         }
828     }
829
830     /**
831      * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
832      * if needed. If the reader was opened in partial mode the object will be released
833      * to save memory.
834      * @param obj the <CODE>PdfObject</CODE> to read
835      * @param parent
836      * @return a PdfObject
837      */

838     public static PdfObject getPdfObjectRelease(PdfObject obj, PdfObject parent) {
839         PdfObject obj2 = getPdfObject(obj, parent);
840         releaseLastXrefPartial(obj);
841         return obj2;
842     }
843
844     /**
845      * @param obj
846      * @param parent
847      * @return a PdfObject
848      */

849     public static PdfObject getPdfObject(PdfObject obj, PdfObject parent) {
850         if (obj == null)
851             return null;
852         if (!obj.isIndirect()) {
853             PRIndirectReference ref = null;
854             if (parent != null && (ref = parent.getIndRef()) != null && ref.getReader().isAppendable()) {
855                 switch (obj.type()) {
856                     case PdfObject.NULL:
857                         obj = new PdfNull();
858                         break;
859                     case PdfObject.BOOLEAN:
860                         obj = new PdfBoolean(((PdfBoolean)obj).booleanValue());
861                         break;
862                     case PdfObject.NAME:
863                         obj = new PdfName(obj.getBytes());
864                         break;
865                 }
866                 obj.setIndRef(ref);
867             }
868             return obj;
869         }
870         return getPdfObject(obj);
871     }
872
873     /**
874      * @param idx
875      * @return a PdfObject
876      */

877     public PdfObject getPdfObjectRelease(int idx) {
878         PdfObject obj = getPdfObject(idx);
879         releaseLastXrefPartial();
880         return obj;
881     }
882
883     /**
884      * @param idx
885      * @return aPdfObject
886      */

887     public PdfObject getPdfObject(int idx) {
888         try {
889             lastXrefPartial = -1;
890             if (idx < 0 || idx >= xrefObj.size())
891                 return null;
892             PdfObject obj = (PdfObject)xrefObj.get(idx);
893             if (!partial || obj != null)
894                 return obj;
895             if (idx * 2 >= xref.length)
896                 return null;
897             obj = readSingleObject(idx);
898             lastXrefPartial = -1;
899             if (obj != null)
900                 lastXrefPartial = idx;
901             return obj;
902         }
903         catch (Exception JavaDoc e) {
904             throw new ExceptionConverter(e);
905         }
906     }
907
908     /**
909      *
910      */

911     public void resetLastXrefPartial() {
912         lastXrefPartial = -1;
913     }
914
915     /**
916      *
917      */

918     public void releaseLastXrefPartial() {
919         if (partial && lastXrefPartial != -1) {
920             xrefObj.set(lastXrefPartial, null);
921             lastXrefPartial = -1;
922         }
923     }
924
925     /**
926      * @param obj
927      */

928     public static void releaseLastXrefPartial(PdfObject obj) {
929         if (obj == null)
930             return;
931         if (!obj.isIndirect())
932             return;
933         PRIndirectReference ref = (PRIndirectReference)obj;
934         PdfReader reader = ref.getReader();
935         if (reader.partial && reader.lastXrefPartial != -1 && reader.lastXrefPartial == ref.getNumber()) {
936             reader.xrefObj.set(reader.lastXrefPartial, null);
937         }
938         reader.lastXrefPartial = -1;
939     }
940
941     private void setXrefPartialObject(int idx, PdfObject obj) {
942         if (!partial || idx < 0)
943             return;
944         xrefObj.set(idx, obj);
945     }
946
947     /**
948      * @param obj
949      * @return an indirect reference
950      */

951     public PRIndirectReference addPdfObject(PdfObject obj) {
952         xrefObj.add(obj);
953         return new PRIndirectReference(this, xrefObj.size() - 1);
954     }
955
956     protected void readPages() throws IOException JavaDoc {
957         catalog = (PdfDictionary)getPdfObject(trailer.get(PdfName.ROOT));
958         rootPages = (PdfDictionary)getPdfObject(catalog.get(PdfName.PAGES));
959         pageRefs = new PageRefs(this);
960     }
961
962     protected void readDocObjPartial() throws IOException JavaDoc {
963         xrefObj = new ArrayList JavaDoc(xref.length / 2);
964         xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
965         readDecryptedDocObj();
966         if (objStmToOffset != null) {
967             int keys[] = objStmToOffset.getKeys();
968             for (int k = 0; k < keys.length; ++k) {
969                 int n = keys[k];
970                 objStmToOffset.put(n, xref[n * 2]);
971                 xref[n * 2] = -1;
972             }
973         }
974     }
975
976     protected PdfObject readSingleObject(int k) throws IOException JavaDoc {
977         strings.clear();
978         int k2 = k * 2;
979         int pos = xref[k2];
980         if (pos < 0)
981             return null;
982         if (xref[k2 + 1] > 0)
983             pos = objStmToOffset.get(xref[k2 + 1]);
984         if (pos == 0)
985             return null;
986         tokens.seek(pos);
987         tokens.nextValidToken();
988         if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
989             tokens.throwError("Invalid object number.");
990         objNum = tokens.intValue();
991         tokens.nextValidToken();
992         if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
993             tokens.throwError("Invalid generation number.");
994         objGen = tokens.intValue();
995         tokens.nextValidToken();
996         if (!tokens.getStringValue().equals("obj"))
997             tokens.throwError("Token 'obj' expected.");
998         PdfObject obj;
999         try {
1000            obj = readPRObject();
1001            for (int j = 0; j < strings.size(); ++j) {
1002                PdfString str = (PdfString)strings.get(j);
1003                str.decrypt(this);
1004            }
1005            if (obj.isStream()) {
1006                checkPRStreamLength((PRStream)obj);
1007            }
1008        }
1009        catch (Exception JavaDoc e) {
1010            obj = null;
1011        }
1012        if (xref[k2 + 1] > 0) {
1013            obj = readOneObjStm((PRStream)obj, xref[k2]);
1014        }
1015        xrefObj.set(k, obj);
1016        return obj;
1017    }
1018
1019    protected PdfObject readOneObjStm(PRStream stream, int idx) throws IOException JavaDoc {
1020        int first = ((PdfNumber)getPdfObject(stream.get(PdfName.FIRST))).intValue();
1021        byte b[] = getStreamBytes(stream, tokens.getFile());
1022        PRTokeniser saveTokens = tokens;
1023        tokens = new PRTokeniser(b);
1024        try {
1025            int address = 0;
1026            boolean ok = true;
1027            ++idx;
1028            for (int k = 0; k < idx; ++k) {
1029                ok = tokens.nextToken();
1030                if (!ok)
1031                    break;
1032                if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1033                    ok = false;
1034                    break;
1035                }
1036                ok = tokens.nextToken();
1037                if (!ok)
1038                    break;
1039                if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1040                    ok = false;
1041                    break;
1042                }
1043                address = tokens.intValue() + first;
1044            }
1045            if (!ok)
1046                throw new IOException JavaDoc("Error reading ObjStm");
1047            tokens.seek(address);
1048            return readPRObject();
1049        }
1050        finally {
1051            tokens = saveTokens;
1052        }
1053    }
1054
1055    /**
1056     * @return the percentage of the cross reference table that has been read
1057     */

1058    public double dumpPerc() {
1059        int total = 0;
1060        for (int k = 0; k < xrefObj.size(); ++k) {
1061            if (xrefObj.get(k) != null)
1062                ++total;
1063        }
1064        return (total * 100.0 / xrefObj.size());
1065    }
1066
1067    protected void readDocObj() throws IOException JavaDoc {
1068        ArrayList JavaDoc streams = new ArrayList JavaDoc();
1069        xrefObj = new ArrayList JavaDoc(xref.length / 2);
1070        xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
1071        for (int k = 2; k < xref.length; k += 2) {
1072            int pos = xref[k];
1073            if (pos <= 0 || xref[k + 1] > 0)
1074                continue;
1075            tokens.seek(pos);
1076            tokens.nextValidToken();
1077            if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1078                tokens.throwError("Invalid object number.");
1079            objNum = tokens.intValue();
1080            tokens.nextValidToken();
1081            if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1082                tokens.throwError("Invalid generation number.");
1083            objGen = tokens.intValue();
1084            tokens.nextValidToken();
1085            if (!tokens.getStringValue().equals("obj"))
1086                tokens.throwError("Token 'obj' expected.");
1087            PdfObject obj;
1088            try {
1089                obj = readPRObject();
1090                if (obj.isStream()) {
1091                    streams.add(obj);
1092                }
1093            }
1094            catch (Exception JavaDoc e) {
1095                obj = null;
1096            }
1097            xrefObj.set(k / 2, obj);
1098        }
1099        for (int k = 0; k < streams.size(); ++k) {
1100            checkPRStreamLength((PRStream)streams.get(k));
1101        }
1102        readDecryptedDocObj();
1103        if (objStmMark != null) {
1104            for (Iterator JavaDoc i = objStmMark.entrySet().iterator(); i.hasNext();) {
1105                Map.Entry JavaDoc entry = (Map.Entry JavaDoc)i.next();
1106                int n = ((Integer JavaDoc)entry.getKey()).intValue();
1107                IntHashtable h = (IntHashtable)entry.getValue();
1108                readObjStm((PRStream)xrefObj.get(n), h);
1109                xrefObj.set(n, null);
1110            }
1111            objStmMark = null;
1112        }
1113        xref = null;
1114    }
1115
1116    private void checkPRStreamLength(PRStream stream) throws IOException JavaDoc {
1117        int fileLength = tokens.length();
1118        int start = stream.getOffset();
1119        boolean calc = false;
1120        int streamLength = 0;
1121        PdfObject obj = getPdfObjectRelease(stream.get(PdfName.LENGTH));
1122        if (obj != null && obj.type() == PdfObject.NUMBER) {
1123            streamLength = ((PdfNumber)obj).intValue();
1124            if (streamLength + start > fileLength - 20)
1125                calc = true;
1126            else {
1127                tokens.seek(start + streamLength);
1128                String JavaDoc line = tokens.readString(20);
1129                if (!line.startsWith("\nendstream") &&
1130                !line.startsWith("\r\nendstream") &&
1131                !line.startsWith("\rendstream") &&
1132                !line.startsWith("endstream"))
1133                    calc = true;
1134            }
1135        }
1136        else
1137            calc = true;
1138        if (calc) {
1139            byte tline[] = new byte[16];
1140            tokens.seek(start);
1141            while (true) {
1142                int pos = tokens.getFilePointer();
1143                if (!tokens.readLineSegment(tline))
1144                    break;
1145                if (equalsn(tline, endstream)) {
1146                    streamLength = pos - start;
1147                    break;
1148                }
1149                if (equalsn(tline, endobj)) {
1150                    tokens.seek(pos - 16);
1151                    String JavaDoc s = tokens.readString(16);
1152                    int index = s.indexOf("endstream");
1153                    if (index >= 0)
1154                        pos = pos - 16 + index;
1155                    streamLength = pos - start;
1156                    break;
1157                }
1158            }
1159        }
1160        stream.setLength(streamLength);
1161    }
1162
1163    protected void readObjStm(PRStream stream, IntHashtable map) throws IOException JavaDoc {
1164        int first = ((PdfNumber)getPdfObject(stream.get(PdfName.FIRST))).intValue();
1165        int n = ((PdfNumber)getPdfObject(stream.get(PdfName.N))).intValue();
1166        byte b[] = getStreamBytes(stream, tokens.getFile());
1167        PRTokeniser saveTokens = tokens;
1168        tokens = new PRTokeniser(b);
1169        try {
1170            int address[] = new int[n];
1171            int objNumber[] = new int[n];
1172            boolean ok = true;
1173            for (int k = 0; k < n; ++k) {
1174                ok = tokens.nextToken();
1175                if (!ok)
1176                    break;
1177                if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1178                    ok = false;
1179                    break;
1180                }
1181                objNumber[k] = tokens.intValue();
1182                ok = tokens.nextToken();
1183                if (!ok)
1184                    break;
1185                if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1186                    ok = false;
1187                    break;
1188                }
1189                address[k] = tokens.intValue() + first;
1190            }
1191            if (!ok)
1192                throw new IOException JavaDoc("Error reading ObjStm");
1193            for (int k = 0; k < n; ++k) {
1194                if (map.containsKey(k)) {
1195                    tokens.seek(address[k]);
1196                    PdfObject obj = readPRObject();
1197                    xrefObj.set(objNumber[k], obj);
1198                }
1199            }
1200        }
1201        finally {
1202            tokens = saveTokens;
1203        }
1204    }
1205
1206    /**
1207     * Eliminates the reference to the object freeing the memory used by it and clearing
1208     * the xref entry.
1209     * @param obj the object. If it's an indirect reference it will be eliminated
1210     * @return the object or the already erased dereferenced object
1211     */

1212    public static PdfObject killIndirect(PdfObject obj) {
1213        if (obj == null || obj.isNull())
1214            return null;
1215        PdfObject ret = getPdfObjectRelease(obj);
1216        if (obj.isIndirect()) {
1217            PRIndirectReference ref = (PRIndirectReference)obj;
1218            PdfReader reader = ref.getReader();
1219            int n = ref.getNumber();
1220            reader.xrefObj.set(n, null);
1221            if (reader.partial)
1222                reader.xref[n * 2] = -1;
1223        }
1224        return ret;
1225    }
1226
1227    private void ensureXrefSize(int size) {
1228        if (size == 0)
1229            return;
1230        if (xref == null)
1231            xref = new int[size];
1232        else {
1233            if (xref.length < size) {
1234                int xref2[] = new int[size];
1235                System.arraycopy(xref, 0, xref2, 0, xref.length);
1236                xref = xref2;
1237            }
1238        }
1239    }
1240
1241    protected void readXref() throws IOException JavaDoc {
1242        hybridXref = false;
1243        newXrefType = false;
1244        tokens.seek(tokens.getStartxref());
1245        tokens.nextToken();
1246        if (!tokens.getStringValue().equals("startxref"))
1247            throw new IOException JavaDoc("startxref not found.");
1248        tokens.nextToken();
1249        if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1250            throw new IOException JavaDoc("startxref is not followed by a number.");
1251        int startxref = tokens.intValue();
1252        lastXref = startxref;
1253        eofPos = tokens.getFilePointer();
1254        try {
1255            if (readXRefStream(startxref)) {
1256                newXrefType = true;
1257                return;
1258            }
1259        }
1260        catch (Exception JavaDoc e) {}
1261        xref = null;
1262        tokens.seek(startxref);
1263        trailer = readXrefSection();
1264        PdfDictionary trailer2 = trailer;
1265        while (true) {
1266            PdfNumber prev = (PdfNumber)trailer2.get(PdfName.PREV);
1267            if (prev == null)
1268                break;
1269            tokens.seek(prev.intValue());
1270            trailer2 = readXrefSection();
1271        }
1272    }
1273
1274    protected PdfDictionary readXrefSection() throws IOException JavaDoc {
1275        tokens.nextValidToken();
1276        if (!tokens.getStringValue().equals("xref"))
1277            tokens.throwError("xref subsection not found");
1278        int start = 0;
1279        int end = 0;
1280        int pos = 0;
1281        int gen = 0;
1282        while (true) {
1283            tokens.nextValidToken();
1284            if (tokens.getStringValue().equals("trailer"))
1285                break;
1286            if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1287                tokens.throwError("Object number of the first object in this xref subsection not found");
1288            start = tokens.intValue();
1289            tokens.nextValidToken();
1290            if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1291                tokens.throwError("Number of entries in this xref subsection not found");
1292            end = tokens.intValue() + start;
1293            if (start == 1) { // fix incorrect start number
1294
int back = tokens.getFilePointer();
1295                tokens.nextValidToken();
1296                pos = tokens.intValue();
1297                tokens.nextValidToken();
1298                gen = tokens.intValue();
1299                if (pos == 0 && gen == 65535) {
1300                    --start;
1301                    --end;
1302                }
1303                tokens.seek(back);
1304            }
1305            ensureXrefSize(end * 2);
1306            for (int k = start; k < end; ++k) {
1307                tokens.nextValidToken();
1308                pos = tokens.intValue();
1309                tokens.nextValidToken();
1310                gen = tokens.intValue();
1311                tokens.nextValidToken();
1312                int p = k * 2;
1313                if (tokens.getStringValue().equals("n")) {
1314                    if (xref[p] == 0 && xref[p + 1] == 0) {
1315// if (pos == 0)
1316
// tokens.throwError("File position 0 cross-reference entry in this xref subsection");
1317
xref[p] = pos;
1318                    }
1319                }
1320                else if (tokens.getStringValue().equals("f")) {
1321                    if (xref[p] == 0 && xref[p + 1] == 0)
1322                        xref[p] = -1;
1323                }
1324                else
1325                    tokens.throwError("Invalid cross-reference entry in this xref subsection");
1326            }
1327        }
1328        PdfDictionary trailer = (PdfDictionary)readPRObject();
1329        PdfNumber xrefSize = (PdfNumber)trailer.get(PdfName.SIZE);
1330        ensureXrefSize(xrefSize.intValue() * 2);
1331        PdfObject xrs = trailer.get(PdfName.XREFSTM);
1332        if (xrs != null && xrs.isNumber()) {
1333            int loc = ((PdfNumber)xrs).intValue();
1334            try {
1335                readXRefStream(loc);
1336                newXrefType = true;
1337                hybridXref = true;
1338            }
1339            catch (IOException JavaDoc e) {
1340                xref = null;
1341                throw e;
1342            }
1343        }
1344        return trailer;
1345    }
1346
1347    protected boolean readXRefStream(int ptr) throws IOException JavaDoc {
1348        tokens.seek(ptr);
1349        int thisStream = 0;
1350        if (!tokens.nextToken())
1351            return false;
1352        if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1353            return false;
1354        thisStream = tokens.intValue();
1355        if (!tokens.nextToken() || tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1356            return false;
1357        if (!tokens.nextToken() || !tokens.getStringValue().equals("obj"))
1358            return false;
1359        PdfObject object = readPRObject();
1360        PRStream stm = null;
1361        if (object.isStream()) {
1362            stm = (PRStream)object;
1363            if (!PdfName.XREF.equals(stm.get(PdfName.TYPE)))
1364                return false;
1365        }
1366        else
1367            return false;
1368        if (trailer == null) {
1369            trailer = new PdfDictionary();
1370            trailer.putAll(stm);
1371        }
1372        stm.setLength(((PdfNumber)stm.get(PdfName.LENGTH)).intValue());
1373        int size = ((PdfNumber)stm.get(PdfName.SIZE)).intValue();
1374        PdfArray index;
1375        PdfObject obj = stm.get(PdfName.INDEX);
1376        if (obj == null) {
1377            index = new PdfArray();
1378            index.add(new int[]{0, size});
1379        }
1380        else
1381            index = (PdfArray)obj;
1382        PdfArray w = (PdfArray)stm.get(PdfName.W);
1383        int prev = -1;
1384        obj = stm.get(PdfName.PREV);
1385        if (obj != null)
1386            prev = ((PdfNumber)obj).intValue();
1387        // Each xref pair is a position
1388
// type 0 -> -1, 0
1389
// type 1 -> offset, 0
1390
// type 2 -> index, obj num
1391
ensureXrefSize(size * 2);
1392        if (objStmMark == null && !partial)
1393            objStmMark = new HashMap JavaDoc();
1394        if (objStmToOffset == null && partial)
1395            objStmToOffset = new IntHashtable();
1396        byte b[] = getStreamBytes(stm, tokens.getFile());
1397        int bptr = 0;
1398        ArrayList JavaDoc wa = w.getArrayList();
1399        int wc[] = new int[3];
1400        for (int k = 0; k < 3; ++k)
1401            wc[k] = ((PdfNumber)wa.get(k)).intValue();
1402        ArrayList JavaDoc sections = index.getArrayList();
1403        for (int idx = 0; idx < sections.size(); idx += 2) {
1404            int start = ((PdfNumber)sections.get(idx)).intValue();
1405            int length = ((PdfNumber)sections.get(idx + 1)).intValue();
1406            ensureXrefSize((start + length) * 2);
1407            while (length-- > 0) {
1408                int type = 1;
1409                if (wc[0] > 0) {
1410                    type = 0;
1411                    for (int k = 0; k < wc[0]; ++k)
1412                        type = (type << 8) + (b[bptr++] & 0xff);
1413                }
1414                int field2 = 0;
1415                for (int k = 0; k < wc[1]; ++k)
1416                    field2 = (field2 << 8) + (b[bptr++] & 0xff);
1417                int field3 = 0;
1418                for (int k = 0; k < wc[2]; ++k)
1419                    field3 = (field3 << 8) + (b[bptr++] & 0xff);
1420                int base = start * 2;
1421                if (xref[base] == 0 && xref[base + 1] == 0) {
1422                    switch (type) {
1423                        case 0:
1424                            xref[base] = -1;
1425                            break;
1426                        case 1:
1427                            xref[base] = field2;
1428                            break;
1429                        case 2:
1430                            xref[base] = field3;
1431                            xref[base + 1] = field2;
1432                            if (partial) {
1433                                objStmToOffset.put(field2, 0);
1434                            }
1435                            else {
1436                                Integer JavaDoc on = new Integer JavaDoc(field2);
1437                                IntHashtable seq = (IntHashtable)objStmMark.get(on);
1438                                if (seq == null) {
1439                                    seq = new IntHashtable();
1440                                    seq.put(field3, 1);
1441                                    objStmMark.put(on, seq);
1442                                }
1443                                else
1444                                    seq.put(field3, 1);
1445                            }
1446                            break;
1447                    }
1448                }
1449                ++start;
1450            }
1451        }
1452        thisStream *= 2;
1453        if (thisStream < xref.length)
1454            xref[thisStream] = -1;
1455
1456        if (prev == -1)
1457            return true;
1458        return readXRefStream(prev);
1459    }
1460
1461    protected void rebuildXref() throws IOException JavaDoc {
1462        hybridXref = false;
1463        newXrefType = false;
1464        tokens.seek(0);
1465        int xr[][] = new int[1024][];
1466        int top = 0;
1467        trailer = null;
1468        byte line[] = new byte[64];
1469        for (;;) {
1470            int pos = tokens.getFilePointer();
1471            if (!tokens.readLineSegment(line))
1472                break;
1473            if (line[0] == 't') {
1474                if (!PdfEncodings.convertToString(line, null).startsWith("trailer"))
1475                    continue;
1476                tokens.seek(pos);
1477                tokens.nextToken();
1478                pos = tokens.getFilePointer();
1479                try {
1480                    PdfDictionary dic = (PdfDictionary)readPRObject();
1481                    if (dic.get(PdfName.ROOT) != null)
1482                        trailer = dic;
1483                    else
1484                        tokens.seek(pos);
1485                }
1486                catch (Exception JavaDoc e) {
1487                    tokens.seek(pos);
1488                }
1489            }
1490            else if (line[0] >= '0' && line[0] <= '9') {
1491                int obj[] = PRTokeniser.checkObjectStart(line);
1492                if (obj == null)
1493                    continue;
1494                int num = obj[0];
1495                int gen = obj[1];
1496                if (num >= xr.length) {
1497                    int newLength = num * 2;
1498                    int xr2[][] = new int[newLength][];
1499                    System.arraycopy(xr, 0, xr2, 0, top);
1500                    xr = xr2;
1501                }
1502                if (num >= top)
1503                    top = num + 1;
1504                if (xr[num] == null || gen >= xr[num][1]) {
1505                    obj[0] = pos;
1506                    xr[num] = obj;
1507                }
1508            }
1509        }
1510        if (trailer == null)
1511            throw new IOException JavaDoc("trailer not found.");
1512        xref = new int[top * 2];
1513        for (int k = 0; k < top; ++k) {
1514            int obj[] = xr[k];
1515            if (obj != null)
1516                xref[k * 2] = obj[0];
1517        }
1518    }
1519
1520    protected PdfDictionary readDictionary() throws IOException JavaDoc {
1521        PdfDictionary dic = new PdfDictionary();
1522        while (true) {
1523            tokens.nextValidToken();
1524            if (tokens.getTokenType() == PRTokeniser.TK_END_DIC)
1525                break;
1526            if (tokens.getTokenType() != PRTokeniser.TK_NAME)
1527                tokens.throwError("Dictionary key is not a name.");
1528            PdfName name = new PdfName(tokens.getStringValue(), false);
1529            PdfObject obj = readPRObject();
1530            int type = obj.type();
1531            if (-type == PRTokeniser.TK_END_DIC)
1532                tokens.throwError("Unexpected '>>'");
1533            if (-type == PRTokeniser.TK_END_ARRAY)
1534                tokens.throwError("Unexpected ']'");
1535            dic.put(name, obj);
1536        }
1537        return dic;
1538    }
1539
1540    protected PdfArray readArray() throws IOException JavaDoc {
1541        PdfArray array = new PdfArray();
1542        while (true) {
1543            PdfObject obj = readPRObject();
1544            int type = obj.type();
1545            if (-type == PRTokeniser.TK_END_ARRAY)
1546                break;
1547            if (-type == PRTokeniser.TK_END_DIC)
1548                tokens.throwError("Unexpected '>>'");
1549            array.add(obj);
1550        }
1551        return array;
1552    }
1553
1554    protected PdfObject readPRObject() throws IOException JavaDoc {
1555        tokens.nextValidToken();
1556        int type = tokens.getTokenType();
1557        switch (type) {
1558            case PRTokeniser.TK_START_DIC: {
1559                PdfDictionary dic = readDictionary();
1560                int pos = tokens.getFilePointer();
1561                // be careful in the trailer. May not be a "next" token.
1562
if (tokens.nextToken() && tokens.getStringValue().equals("stream")) {
1563                    int ch = tokens.read();
1564                    if (ch != '\n')
1565                        ch = tokens.read();
1566                    if (ch != '\n')
1567                        tokens.backOnePosition(ch);
1568                    PRStream stream = new PRStream(this, tokens.getFilePointer());
1569                    stream.putAll(dic);
1570                    stream.setObjNum(objNum, objGen);
1571                    return stream;
1572                }
1573                else {
1574                    tokens.seek(pos);
1575                    return dic;
1576                }
1577            }
1578            case PRTokeniser.TK_START_ARRAY:
1579                return readArray();
1580            case PRTokeniser.TK_NUMBER:
1581                return new PdfNumber(tokens.getStringValue());
1582            case PRTokeniser.TK_STRING:
1583                PdfString str = new PdfString(tokens.getStringValue(), null).setHexWriting(tokens.isHexString());
1584                str.setObjNum(objNum, objGen);
1585                if (strings != null)
1586                    strings.add(str);
1587                return str;
1588            case PRTokeniser.TK_NAME:
1589                return new PdfName(tokens.getStringValue(), false);
1590            case PRTokeniser.TK_REF:
1591                int num = tokens.getReference();
1592                PRIndirectReference ref = new PRIndirectReference(this, num, tokens.getGeneration());
1593                return ref;
1594            default:
1595                String JavaDoc sv = tokens.getStringValue();
1596                if ("null".equals(sv))
1597                    return PdfNull.PDFNULL;
1598                else if ("true".equals(sv))
1599                    return PdfBoolean.PDFTRUE;
1600                else if ("false".equals(sv))
1601                    return PdfBoolean.PDFFALSE;
1602                return new PdfLiteral(-type, tokens.getStringValue());
1603        }
1604    }
1605
1606    /** Decodes a stream that has the FlateDecode filter.
1607     * @param in the input data
1608     * @return the decoded data
1609     */

1610    public static byte[] FlateDecode(byte in[]) {
1611        byte b[] = FlateDecode(in, true);
1612        if (b == null)
1613            return FlateDecode(in, false);
1614        return b;
1615    }
1616
1617    /**
1618     * @param in
1619     * @param dicPar
1620     * @return a byte array
1621     */

1622    public static byte[] decodePredictor(byte in[], PdfObject dicPar) {
1623        if (dicPar == null || !dicPar.isDictionary())
1624            return in;
1625        PdfDictionary dic = (PdfDictionary)dicPar;
1626        PdfObject obj = getPdfObject(dic.get(PdfName.PREDICTOR));
1627        if (obj == null || !obj.isNumber())
1628            return in;
1629        int predictor = ((PdfNumber)obj).intValue();
1630        if (predictor < 10)
1631            return in;
1632        int width = 1;
1633        obj = getPdfObject(dic.get(PdfName.COLUMNS));
1634        if (obj != null && obj.isNumber())
1635            width = ((PdfNumber)obj).intValue();
1636        int colors = 1;
1637        obj = getPdfObject(dic.get(PdfName.COLORS));
1638        if (obj != null && obj.isNumber())
1639            colors = ((PdfNumber)obj).intValue();
1640        int bpc = 8;
1641        obj = getPdfObject(dic.get(PdfName.BITSPERCOMPONENT));
1642        if (obj != null && obj.isNumber())
1643            bpc = ((PdfNumber)obj).intValue();
1644        DataInputStream JavaDoc dataStream = new DataInputStream JavaDoc(new ByteArrayInputStream JavaDoc(in));
1645        ByteArrayOutputStream JavaDoc fout = new ByteArrayOutputStream JavaDoc(in.length);
1646        int bytesPerPixel = colors * bpc / 8;
1647        int bytesPerRow = (colors*width*bpc + 7)/8;
1648        byte[] curr = new byte[bytesPerRow];
1649        byte[] prior = new byte[bytesPerRow];
1650
1651        // Decode the (sub)image row-by-row
1652
while (true) {
1653            // Read the filter type byte and a row of data
1654
int filter = 0;
1655            try {
1656                filter = dataStream.read();
1657                if (filter < 0) {
1658                    return fout.toByteArray();
1659                }
1660                dataStream.readFully(curr, 0, bytesPerRow);
1661            } catch (Exception JavaDoc e) {
1662                return fout.toByteArray();
1663            }
1664
1665            switch (filter) {
1666                case 0: //PNG_FILTER_NONE
1667
break;
1668                case 1: //PNG_FILTER_SUB
1669
for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1670                        curr[i] += curr[i - bytesPerPixel];
1671                    }
1672                    break;
1673                case 2: //PNG_FILTER_UP
1674
for (int i = 0; i < bytesPerRow; i++) {
1675                        curr[i] += prior[i];
1676                    }
1677                    break;
1678                case 3: //PNG_FILTER_AVERAGE
1679
for (int i = 0; i < bytesPerPixel; i++) {
1680                        curr[i] += prior[i] / 2;
1681                    }
1682                    for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1683                        curr[i] += ((curr[i - bytesPerPixel] & 0xff) + (prior[i] & 0xff))/2;
1684                    }
1685                    break;
1686                case 4: //PNG_FILTER_PAETH
1687
for (int i = 0; i < bytesPerPixel; i++) {
1688                        curr[i] += prior[i];
1689                    }
1690
1691                    for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1692                        int a = curr[i - bytesPerPixel] & 0xff;
1693                        int b = prior[i] & 0xff;
1694                        int c = prior[i - bytesPerPixel] & 0xff;
1695
1696                        int p = a + b - c;
1697                        int pa = Math.abs(p - a);
1698                        int pb = Math.abs(p - b);
1699                        int pc = Math.abs(p - c);
1700
1701                        int ret;
1702
1703                        if ((pa <= pb) && (pa <= pc)) {
1704                            ret = a;
1705                        } else if (pb <= pc) {
1706                            ret = b;
1707                        } else {
1708                            ret = c;
1709                        }
1710                        curr[i] += (byte)(ret);
1711                    }
1712                    break;
1713                default:
1714                    // Error -- uknown filter type
1715
throw new RuntimeException JavaDoc("PNG filter unknown.");
1716            }
1717            try {
1718                fout.write(curr);
1719            }
1720            catch (IOException JavaDoc ioe) {
1721                // Never happens
1722
}
1723
1724            // Swap curr and prior
1725
byte[] tmp = prior;
1726            prior = curr;
1727            curr = tmp;
1728        }
1729    }
1730
1731    /** A helper to FlateDecode.
1732     * @param in the input data
1733     * @param strict <CODE>true</CODE> to read a correct stream. <CODE>false</CODE>
1734     * to try to read a corrupted stream
1735     * @return the decoded data
1736     */

1737    public static byte[] FlateDecode(byte in[], boolean strict) {
1738        ByteArrayInputStream JavaDoc stream = new ByteArrayInputStream JavaDoc(in);
1739        InflaterInputStream JavaDoc zip = new InflaterInputStream JavaDoc(stream);
1740        ByteArrayOutputStream JavaDoc out = new ByteArrayOutputStream JavaDoc();
1741        byte b[] = new byte[strict ? 4092 : 1];
1742        try {
1743            int n;
1744            while ((n = zip.read(b)) >= 0) {
1745                out.write(b, 0, n);
1746            }
1747            zip.close();
1748            out.close();
1749            return out.toByteArray();
1750        }
1751        catch (Exception JavaDoc e) {
1752            if (strict)
1753                return null;
1754            return out.toByteArray();
1755        }
1756    }
1757
1758    /** Decodes a stream that has the ASCIIHexDecode filter.
1759     * @param in the input data
1760     * @return the decoded data
1761     */

1762    public static byte[] ASCIIHexDecode(byte in[]) {
1763        ByteArrayOutputStream JavaDoc out = new ByteArrayOutputStream JavaDoc();
1764        boolean first = true;
1765        int n1 = 0;
1766        for (int k = 0; k < in.length; ++k) {
1767            int ch = in[k] & 0xff;
1768            if (ch == '>')
1769                break;
1770            if (PRTokeniser.isWhitespace(ch))
1771                continue;
1772            int n = PRTokeniser.getHex(ch);
1773            if (n == -1)
1774                throw new RuntimeException JavaDoc("Illegal character in ASCIIHexDecode.");
1775            if (first)
1776                n1 = n;
1777            else
1778                out.write((byte)((n1 << 4) + n));
1779            first = !first;
1780        }
1781        if (!first)
1782            out.write((byte)(n1 << 4));
1783        return out.toByteArray();
1784    }
1785
1786    /** Decodes a stream that has the ASCII85Decode filter.
1787     * @param in the input data
1788     * @return the decoded data
1789     */

1790    public static byte[] ASCII85Decode(byte in[]) {
1791        ByteArrayOutputStream JavaDoc out = new ByteArrayOutputStream JavaDoc();
1792        int state = 0;
1793        int chn[] = new int[5];
1794        for (int k = 0; k < in.length; ++k) {
1795            int ch = in[k] & 0xff;
1796            if (ch == '~')
1797                break;
1798            if (PRTokeniser.isWhitespace(ch))
1799                continue;
1800            if (ch == 'z' && state == 0) {
1801                out.write(0);
1802                out.write(0);
1803                out.write(0);
1804                out.write(0);
1805                continue;
1806            }
1807            if (ch < '!' || ch > 'u')
1808                throw new RuntimeException JavaDoc("Illegal character in ASCII85Decode.");
1809            chn[state] = ch - '!';
1810            ++state;
1811            if (state == 5) {
1812                state = 0;
1813                int r = 0;
1814                for (int j = 0; j < 5; ++j)
1815                    r = r * 85 + chn[j];
1816                out.write((byte)(r >> 24));
1817                out.write((byte)(r >> 16));
1818                out.write((byte)(r >> 8));
1819                out.write((byte)r);
1820            }
1821        }
1822        int r = 0;
1823        if (state == 1)
1824            throw new RuntimeException JavaDoc("Illegal length in ASCII85Decode.");
1825        if (state == 2) {
1826            r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85;
1827            out.write((byte)(r >> 24));
1828        }
1829        else if (state == 3) {
1830            r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + chn[2] * 85 * 85;
1831            out.write((byte)(r >> 24));
1832            out.write((byte)(r >> 16));
1833        }
1834        else if (state == 4) {
1835            r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + chn[2] * 85 * 85 + chn[3] * 85 ;
1836            out.write((byte)(r >> 24));
1837            out.write((byte)(r >> 16));
1838            out.write((byte)(r >> 8));
1839        }
1840        return out.toByteArray();
1841    }
1842
1843    /** Decodes a stream that has the LZWDecode filter.
1844     * @param in the input data
1845     * @return the decoded data
1846     */

1847    public static byte[] LZWDecode(byte in[]) {
1848        ByteArrayOutputStream JavaDoc out = new ByteArrayOutputStream JavaDoc();
1849        LZWDecoder lzw = new LZWDecoder();
1850        lzw.decode(in, out);
1851        return out.toByteArray();
1852    }
1853
1854    /** Checks if the document had errors and was rebuilt.
1855     * @return true if rebuilt.
1856     *
1857     */

1858    public boolean isRebuilt() {
1859        return this.rebuilt;
1860    }
1861
1862    /** Gets the dictionary that represents a page.
1863     * @param pageNum the page number. 1 is the first
1864     * @return the page dictionary
1865     */

1866    public PdfDictionary getPageN(int pageNum) {
1867        PdfDictionary dic = pageRefs.getPageN(pageNum);
1868        if (dic == null)
1869            return null;
1870        if (appendable)
1871            dic.setIndRef(pageRefs.getPageOrigRef(pageNum));
1872        return dic;
1873    }
1874
1875    /**
1876     * @param pageNum
1877     * @return a Dictionary object
1878     */

1879    public PdfDictionary getPageNRelease(int pageNum) {
1880        PdfDictionary dic = getPageN(pageNum);
1881        pageRefs.releasePage(pageNum);
1882        return dic;
1883    }
1884
1885    /**
1886     * @param pageNum
1887     */

1888    public void releasePage(int pageNum) {
1889        pageRefs.releasePage(pageNum);
1890    }
1891
1892    /**
1893     *
1894     */

1895    public void resetReleasePage() {
1896        pageRefs.resetReleasePage();
1897    }
1898
1899    /** Gets the page reference to this page.
1900     * @param pageNum the page number. 1 is the first
1901     * @return the page reference
1902     */

1903    public PRIndirectReference getPageOrigRef(int pageNum) {
1904        return pageRefs.getPageOrigRef(pageNum);
1905    }
1906
1907    /** Gets the contents of the page.
1908     * @param pageNum the page number. 1 is the first
1909     * @param file the location of the PDF document
1910     * @throws IOException on error
1911     * @return the content
1912     */

1913    public byte[] getPageContent(int pageNum, RandomAccessFileOrArray file) throws IOException JavaDoc{
1914        PdfDictionary page = getPageNRelease(pageNum);
1915        if (page == null)
1916            return null;
1917        PdfObject contents = getPdfObjectRelease(page.get(PdfName.CONTENTS));
1918        if (contents == null)
1919            return new byte[0];
1920        ByteArrayOutputStream JavaDoc bout = null;
1921        if (contents.isStream()) {
1922            return getStreamBytes((PRStream)contents, file);
1923        }
1924        else if (contents.isArray()) {
1925            PdfArray array = (PdfArray)contents;
1926            ArrayList JavaDoc list = array.getArrayList();
1927            bout = new ByteArrayOutputStream JavaDoc();
1928            for (int k = 0; k < list.size(); ++k) {
1929                PdfObject item = getPdfObjectRelease((PdfObject)list.get(k));
1930                if (item == null || !item.isStream())
1931                    continue;
1932                byte[] b = getStreamBytes((PRStream)item, file);
1933                bout.write(b);
1934                if (k != list.size() - 1)
1935                    bout.write('\n');
1936            }
1937            return bout.toByteArray();
1938        }
1939        else
1940            return new byte[0];
1941    }
1942
1943    /** Gets the contents of the page.
1944     * @param pageNum the page number. 1 is the first
1945     * @throws IOException on error
1946     * @return the content
1947     */

1948    public byte[] getPageContent(int pageNum) throws IOException JavaDoc{
1949        RandomAccessFileOrArray rf = getSafeFile();
1950        try {
1951            rf.reOpen();
1952            return getPageContent(pageNum, rf);
1953        }
1954        finally {
1955            try{rf.close();}catch(Exception JavaDoc e){}
1956        }
1957    }
1958
1959    protected void killXref(PdfObject obj) {
1960        if (obj == null)
1961            return;
1962        if ((obj instanceof PdfIndirectReference) && !obj.isIndirect())
1963            return;
1964        switch (obj.type()) {
1965            case PdfObject.INDIRECT: {
1966                int xr = ((PRIndirectReference)obj).getNumber();
1967                obj = (PdfObject)xrefObj.get(xr);
1968                xrefObj.set(xr, null);
1969                freeXref = xr;
1970                killXref(obj);
1971                break;
1972            }
1973            case PdfObject.ARRAY: {
1974                ArrayList JavaDoc t = ((PdfArray)obj).getArrayList();
1975                for (int i = 0; i < t.size(); ++i)
1976                    killXref((PdfObject)t.get(i));
1977                break;
1978            }
1979            case PdfObject.STREAM:
1980            case PdfObject.DICTIONARY: {
1981                PdfDictionary dic = (PdfDictionary)obj;
1982                for (Iterator JavaDoc i = dic.getKeys().iterator(); i.hasNext();){
1983                    killXref(dic.get((PdfName)i.next()));
1984                }
1985                break;
1986            }
1987        }
1988    }
1989
1990    /** Sets the contents of the page.
1991     * @param content the new page content
1992     * @param pageNum the page number. 1 is the first
1993     */

1994    public void setPageContent(int pageNum, byte content[]) {
1995        PdfDictionary page = getPageN(pageNum);
1996        if (page == null)
1997            return;
1998        PdfObject contents = page.get(PdfName.CONTENTS);
1999        freeXref = -1;
2000        killXref(contents);
2001        if (freeXref == -1) {
2002            xrefObj.add(null);
2003            freeXref = xrefObj.size() - 1;
2004        }
2005        page.put(PdfName.CONTENTS, new PRIndirectReference(this, freeXref));
2006        xrefObj.set(freeXref, new PRStream(this, content));
2007    }
2008
2009    /** Get the content from a stream applying the required filters.
2010     * @param stream the stream
2011     * @param file the location where the stream is
2012     * @throws IOException on error
2013     * @return the stream content
2014     */

2015    public static byte[] getStreamBytes(PRStream stream, RandomAccessFileOrArray file) throws IOException JavaDoc {
2016        PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER));
2017        byte[] b = getStreamBytesRaw(stream, file);
2018        ArrayList JavaDoc filters = new ArrayList JavaDoc();
2019        if (filter != null) {
2020            if (filter.isName())
2021                filters.add(filter);
2022            else if (filter.isArray())
2023                filters = ((PdfArray)filter).getArrayList();
2024        }
2025        ArrayList JavaDoc dp = new ArrayList JavaDoc();
2026        PdfObject dpo = getPdfObjectRelease(stream.get(PdfName.DECODEPARMS));
2027        if (dpo == null || (!dpo.isDictionary() && !dpo.isArray()))
2028            dpo = getPdfObjectRelease(stream.get(PdfName.DP));
2029        if (dpo != null) {
2030            if (dpo.isDictionary())
2031                dp.add(dpo);
2032            else if (dpo.isArray())
2033                dp = ((PdfArray)dpo).getArrayList();
2034        }
2035        String JavaDoc name;
2036        for (int j = 0; j < filters.size(); ++j) {
2037            name = ((PdfName)getPdfObjectRelease((PdfObject)filters.get(j))).toString();
2038            if (name.equals("/FlateDecode") || name.equals("/Fl")) {
2039                b = FlateDecode(b);
2040                PdfObject dicParam = null;
2041                if (j < dp.size()) {
2042                    dicParam = (PdfObject)dp.get(j);
2043                    b = decodePredictor(b, dicParam);
2044                }
2045            }
2046            else if (name.equals("/ASCIIHexDecode") || name.equals("/AHx"))
2047                b = ASCIIHexDecode(b);
2048            else if (name.equals("/ASCII85Decode") || name.equals("/A85"))
2049                b = ASCII85Decode(b);
2050            else if (name.equals("/LZWDecode")) {
2051                b = LZWDecode(b);
2052                PdfObject dicParam = null;
2053                if (j < dp.size()) {
2054                    dicParam = (PdfObject)dp.get(j);
2055                    b = decodePredictor(b, dicParam);
2056                }
2057            }
2058            else if (name.equals("/Crypt")) {
2059            }
2060            else
2061                throw new IOException JavaDoc("The filter " + name + " is not supported.");
2062        }
2063        return b;
2064    }
2065
2066    /** Get the content from a stream applying the required filters.
2067     * @param stream the stream
2068     * @throws IOException on error
2069     * @return the stream content
2070     */

2071    public static byte[] getStreamBytes(PRStream stream) throws IOException JavaDoc {
2072        RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
2073        try {
2074            rf.reOpen();
2075            return getStreamBytes(stream, rf);
2076        }
2077        finally {
2078            try{rf.close();}catch(Exception JavaDoc e){}
2079        }
2080    }
2081
2082    /** Get the content from a stream as it is without applying any filter.
2083     * @param stream the stream
2084     * @param file the location where the stream is
2085     * @throws IOException on error
2086     * @return the stream content
2087     */

2088    public static byte[] getStreamBytesRaw(PRStream stream, RandomAccessFileOrArray file) throws IOException JavaDoc {
2089        PdfReader reader = stream.getReader();
2090        byte b[];
2091        if (stream.getOffset() < 0)
2092            b = stream.getBytes();
2093        else {
2094            b = new byte[stream.getLength()];
2095            file.seek(stream.getOffset());
2096            file.readFully(b);
2097            PdfEncryption decrypt = reader.getDecrypt();
2098            if (decrypt != null) {
2099                PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER));
2100                ArrayList JavaDoc filters = new ArrayList JavaDoc();
2101                if (filter != null) {
2102                    if (filter.isName())
2103                        filters.add(filter);
2104                    else if (filter.isArray())
2105                        filters = ((PdfArray)filter).getArrayList();
2106                }
2107                boolean skip = false;
2108                for (int k = 0; k < filters.size(); ++k) {
2109                    PdfObject obj = getPdfObjectRelease((PdfObject)filters.get(k));
2110                    if (obj != null && obj.toString().equals("/Crypt")) {
2111                        skip = true;
2112                        break;
2113                    }
2114                }
2115                if (!skip) {
2116                    decrypt.setHashKey(stream.getObjNum(), stream.getObjGen());
2117                    b = decrypt.decryptByteArray(b);
2118                }
2119            }
2120        }
2121        return b;
2122    }
2123
2124    /** Get the content from a stream as it is without applying any filter.
2125     * @param stream the stream
2126     * @throws IOException on error
2127     * @return the stream content
2128     */

2129    public static byte[] getStreamBytesRaw(PRStream stream) throws IOException JavaDoc {
2130        RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
2131        try {
2132            rf.reOpen();
2133            return getStreamBytesRaw(stream, rf);
2134        }
2135        finally {
2136            try{rf.close();}catch(Exception JavaDoc e){}
2137        }
2138    }
2139
2140    /** Eliminates shared streams if they exist. */
2141    public void eliminateSharedStreams() {
2142        if (!sharedStreams)
2143            return;
2144        sharedStreams = false;
2145        if (pageRefs.size() == 1)
2146            return;
2147        ArrayList JavaDoc newRefs = new ArrayList JavaDoc();
2148        ArrayList JavaDoc newStreams = new ArrayList JavaDoc();
2149        IntHashtable visited = new IntHashtable();
2150        for (int k = 1; k <= pageRefs.size(); ++k) {
2151            PdfDictionary page = pageRefs.getPageN(k);
2152            if (page == null)
2153                continue;
2154            PdfObject contents = getPdfObject(page.get(PdfName.CONTENTS));
2155            if (contents == null)
2156                continue;
2157            if (contents.isStream()) {
2158                PRIndirectReference ref = (PRIndirectReference)page.get(PdfName.CONTENTS);
2159                if (visited.containsKey(ref.getNumber())) {
2160                    // need to duplicate
2161
newRefs.add(ref);
2162                    newStreams.add(new PRStream((PRStream)contents, null));
2163                }
2164                else
2165                    visited.put(ref.getNumber(), 1);
2166            }
2167            else if (contents.isArray()) {
2168                PdfArray array = (PdfArray)contents;
2169                ArrayList JavaDoc list = array.getArrayList();
2170                for (int j = 0; j < list.size(); ++j) {
2171                    PRIndirectReference ref = (PRIndirectReference)list.get(j);
2172                    if (visited.containsKey(ref.getNumber())) {
2173                        // need to duplicate
2174
newRefs.add(ref);
2175                        newStreams.add(new PRStream((PRStream)getPdfObject(ref), null));
2176                    }
2177                    else
2178                        visited.put(ref.getNumber(), 1);
2179                }
2180            }
2181        }
2182        if (newStreams.isEmpty())
2183            return;
2184        for (int k = 0; k < newStreams.size(); ++k) {
2185            xrefObj.add(newStreams.get(k));
2186            PRIndirectReference ref = (PRIndirectReference)newRefs.get(k);
2187            ref.setNumber(xrefObj.size() - 1, 0);
2188        }
2189    }
2190
2191    /** Checks if the document was changed.
2192     * @return <CODE>true</CODE> if the document was changed,
2193     * <CODE>false</CODE> otherwise
2194     */

2195    public boolean isTampered() {
2196        return tampered;
2197    }
2198
2199    /**
2200     * Sets the tampered state. A tampered PdfReader cannot be reused in PdfStamper.
2201     * @param tampered the tampered state
2202     */

2203    public void setTampered(boolean tampered) {
2204        this.tampered = tampered;
2205    }
2206
2207    /** Gets the XML metadata.
2208     * @throws IOException on error
2209     * @return the XML metadata
2210     */

2211    public byte[] getMetadata() throws IOException JavaDoc {
2212        PdfObject obj = getPdfObject(catalog.get(PdfName.METADATA));
2213        if (!(obj instanceof PRStream))
2214            return null;
2215        RandomAccessFileOrArray rf = getSafeFile();
2216        byte b[] = null;
2217        try {
2218            rf.reOpen();
2219            b = getStreamBytes((PRStream)obj, rf);
2220        }
2221        finally {
2222            try {
2223                rf.close();
2224            }
2225            catch (Exception JavaDoc e) {
2226                // empty on purpose
2227
}
2228        }
2229        return b;
2230    }
2231
2232    /**
2233     * Gets the byte address of the last xref table.
2234     * @return the byte address of the last xref table
2235     */

2236    public int getLastXref() {
2237        return lastXref;
2238    }
2239
2240    /**
2241     * Gets the number of xref objects.
2242     * @return the number of xref objects
2243     */

2244    public int getXrefSize() {
2245        return xrefObj.size();
2246    }
2247
2248    /**
2249     * Gets the byte address of the %%EOF marker.
2250     * @return the byte address of the %%EOF marker
2251     */

2252    public int getEofPos() {
2253        return eofPos;
2254    }
2255
2256    /**
2257     * Gets the PDF version. Only the last version char is returned. For example
2258     * version 1.4 is returned as '4'.
2259     * @return the PDF version
2260     */

2261    public char getPdfVersion() {
2262        return pdfVersion;
2263    }
2264
2265    /**
2266     * Returns <CODE>true</CODE> if the PDF is encrypted.
2267     * @return <CODE>true</CODE> if the PDF is encrypted
2268     */

2269    public boolean isEncrypted() {
2270        return encrypted;
2271    }
2272
2273    /**
2274     * Gets the encryption permissions. It can be used directly in
2275     * <CODE>PdfWriter.setEncryption()</CODE>.
2276     * @return the encryption permissions
2277     */

2278    public int getPermissions() {
2279        return pValue;
2280    }
2281
2282    /**
2283     * Returns <CODE>true</CODE> if the PDF has a 128 bit key encryption.
2284     * @return <CODE>true</CODE> if the PDF has a 128 bit key encryption
2285     */

2286    public boolean is128Key() {
2287        return rValue == 3;
2288    }
2289
2290    /**
2291     * Gets the trailer dictionary
2292     * @return the trailer dictionary
2293     */

2294    public PdfDictionary getTrailer() {
2295        return trailer;
2296    }
2297
2298    PdfEncryption getDecrypt() {
2299        return decrypt;
2300    }
2301
2302    static boolean equalsn(byte a1[], byte a2[]) {
2303        int length = a2.length;
2304        for (int k = 0; k < length; ++k) {
2305            if (a1[k] != a2[k])
2306                return false;
2307        }
2308        return true;
2309    }
2310
2311    static boolean existsName(PdfDictionary dic, PdfName key, PdfName value) {
2312        PdfObject type = getPdfObjectRelease(dic.get(key));
2313        if (type == null || !type.isName())
2314            return false;
2315        PdfName name = (PdfName)type;
2316        return name.equals(value);
2317    }
2318
2319    static String JavaDoc getFontName(PdfDictionary dic) {
2320        if (dic == null)
2321            return null;
2322        PdfObject type = getPdfObjectRelease(dic.get(PdfName.BASEFONT));
2323        if (type == null || !type.isName())
2324            return null;
2325        return PdfName.decodeName(type.toString());
2326    }
2327
2328    static String JavaDoc getSubsetPrefix(PdfDictionary dic) {
2329        if (dic == null)
2330            return null;
2331        String JavaDoc s = getFontName(dic);
2332        if (s == null)
2333            return null;
2334        if (s.length() < 8 || s.charAt(6) != '+')
2335            return null;
2336        for (int k = 0; k < 6; ++k) {
2337            char c = s.charAt(k);
2338            if (c < 'A' || c > 'Z')
2339                return null;
2340        }
2341        return s;
2342    }
2343
2344    /** Finds all the font subsets and changes the prefixes to some
2345     * random values.
2346     * @return the number of font subsets altered
2347     */

2348    public int shuffleSubsetNames() {
2349        int total = 0;
2350        for (int k = 1; k < xrefObj.size(); ++k) {
2351            PdfObject obj = getPdfObjectRelease(k);
2352            if (obj == null || !obj.isDictionary())
2353                continue;
2354            PdfDictionary dic = (PdfDictionary)obj;
2355            if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
2356                continue;
2357            if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
2358                || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
2359                || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) {
2360                String JavaDoc s = getSubsetPrefix(dic);
2361                if (s == null)
2362                    continue;
2363                String JavaDoc ns = BaseFont.createSubsetPrefix() + s.substring(7);
2364                PdfName newName = new PdfName(ns);
2365                dic.put(PdfName.BASEFONT, newName);
2366                setXrefPartialObject(k, dic);
2367                ++total;
2368                PdfDictionary fd = (PdfDictionary)getPdfObject(dic.get(PdfName.FONTDESCRIPTOR));
2369                if (fd == null)
2370                    continue;
2371                fd.put(PdfName.FONTNAME, newName);
2372            }
2373            else if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE0)) {
2374                String JavaDoc s = getSubsetPrefix(dic);
2375                PdfArray arr = (PdfArray)getPdfObject(dic.get(PdfName.DESCENDANTFONTS));
2376                if (arr == null)
2377                    continue;
2378                ArrayList JavaDoc list = arr.getArrayList();
2379                if (list.isEmpty())
2380                    continue;
2381                PdfDictionary desc = (PdfDictionary)getPdfObject((PdfObject)list.get(0));
2382                String JavaDoc sde = getSubsetPrefix(desc);
2383                if (sde == null)
2384                    continue;
2385                String JavaDoc ns = BaseFont.createSubsetPrefix();
2386                if (s != null)
2387                    dic.put(PdfName.BASEFONT, new PdfName(ns + s.substring(7)));
2388                setXrefPartialObject(k, dic);
2389                PdfName newName = new PdfName(ns + sde.substring(7));
2390                desc.put(PdfName.BASEFONT, newName);
2391                ++total;
2392                PdfDictionary fd = (PdfDictionary)getPdfObject(desc.get(PdfName.FONTDESCRIPTOR));
2393                if (fd == null)
2394                    continue;
2395                fd.put(PdfName.FONTNAME, newName);
2396            }
2397        }
2398        return total;
2399    }
2400
2401    /** Finds all the fonts not subset but embedded and marks them as subset.
2402     * @return the number of fonts altered
2403     */

2404    public int createFakeFontSubsets() {
2405        int total = 0;
2406        for (int k = 1; k < xrefObj.size(); ++k) {
2407            PdfObject obj = getPdfObjectRelease(k);
2408            if (obj == null || !obj.isDictionary())
2409                continue;
2410            PdfDictionary dic = (PdfDictionary)obj;
2411            if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
2412                continue;
2413            if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
2414                || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
2415                || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) {
2416                String JavaDoc s = getSubsetPrefix(dic);
2417                if (s != null)
2418                    continue;
2419                s = getFontName(dic);
2420                if (s == null)
2421                    continue;
2422                String JavaDoc ns = BaseFont.createSubsetPrefix() + s;
2423                PdfDictionary fd = (PdfDictionary)getPdfObjectRelease(dic.get(PdfName.FONTDESCRIPTOR));
2424                if (fd == null)
2425                    continue;
2426                if (fd.get(PdfName.FONTFILE) == null && fd.get(PdfName.FONTFILE2) == null
2427                    && fd.get(PdfName.FONTFILE3) == null)
2428                    continue;
2429                fd = (PdfDictionary)getPdfObject(dic.get(PdfName.FONTDESCRIPTOR));
2430                PdfName newName = new PdfName(ns);
2431                dic.put(PdfName.BASEFONT, newName);
2432                fd.put(PdfName.FONTNAME, newName);
2433                setXrefPartialObject(k, dic);
2434                ++total;
2435            }
2436        }
2437        return total;
2438    }
2439
2440    private static PdfArray getNameArray(PdfObject obj) {
2441        if (obj == null)
2442            return null;
2443        obj = getPdfObjectRelease(obj);
2444        if (obj == null)
2445            return null;
2446        if (obj.isArray())
2447            return (PdfArray)obj;
2448        else if (obj.isDictionary()) {
2449            PdfObject arr2 = getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.D));
2450            if (arr2 != null && arr2.isArray())
2451                return (PdfArray)arr2;
2452        }
2453        return null;
2454    }
2455
2456    /**
2457     * Gets all the named destinations as an <CODE>HashMap</CODE>. The key is the name
2458     * and the value is the destinations array.
2459     * @return gets all the named destinations
2460     */

2461    public HashMap JavaDoc getNamedDestination() {
2462        HashMap JavaDoc names = getNamedDestinationFromNames();
2463        names.putAll(getNamedDestinationFromStrings());
2464        return names;
2465    }
2466
2467    /**
2468     * Gets the named destinations from the /Dests key in the catalog as an <CODE>HashMap</CODE>. The key is the name
2469     * and the value is the destinations array.
2470     * @return gets the named destinations
2471     */

2472    public HashMap JavaDoc getNamedDestinationFromNames() {
2473        HashMap JavaDoc names = new HashMap JavaDoc();
2474        if (catalog.get(PdfName.DESTS) != null) {
2475            PdfDictionary dic = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.DESTS));
2476            if (dic == null)
2477                return names;
2478            Set JavaDoc keys = dic.getKeys();
2479            for (Iterator JavaDoc it = keys.iterator(); it.hasNext();) {
2480                PdfName key = (PdfName)it.next();
2481                String JavaDoc name = PdfName.decodeName(key.toString());
2482                PdfArray arr = getNameArray(dic.get(key));
2483                if (arr != null)
2484                    names.put(name, arr);
2485            }
2486        }
2487        return names;
2488    }
2489
2490    /**
2491     * Gets the named destinations from the /Names key in the catalog as an <CODE>HashMap</CODE>. The key is the name
2492     * and the value is the destinations array.
2493     * @return gets the named destinations
2494     */

2495    public HashMap JavaDoc getNamedDestinationFromStrings() {
2496        if (catalog.get(PdfName.NAMES) != null) {
2497            PdfDictionary dic = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.NAMES));
2498            if (dic != null) {
2499                dic = (PdfDictionary)getPdfObjectRelease(dic.get(PdfName.DESTS));
2500                if (dic != null) {
2501                    HashMap JavaDoc names = PdfNameTree.readTree(dic);
2502                    for (Iterator JavaDoc it = names.entrySet().iterator(); it.hasNext();) {
2503                        Map.Entry JavaDoc entry = (Map.Entry JavaDoc)it.next();
2504                        PdfArray arr = getNameArray((PdfObject)entry.getValue());
2505                        if (arr != null)
2506                            entry.setValue(arr);
2507                        else
2508                            it.remove();
2509                    }
2510                    return names;
2511                }
2512            }
2513        }
2514        return new HashMap JavaDoc();
2515    }
2516
2517    private boolean replaceNamedDestination(PdfObject obj, HashMap JavaDoc names) {
2518        obj = getPdfObject(obj);
2519        int objIdx = lastXrefPartial;
2520        releaseLastXrefPartial();
2521        if (obj != null && obj.isDictionary()) {
2522            PdfObject ob2 = getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.DEST));
2523            String JavaDoc name = null;
2524            if (ob2 != null) {
2525                if (ob2.isName())
2526                    name = PdfName.decodeName(ob2.toString());
2527                else if (ob2.isString())
2528                    name = ob2.toString();
2529                PdfArray dest = (PdfArray)names.get(name);
2530                if (dest != null) {
2531                    ((PdfDictionary)obj).put(PdfName.DEST, dest);
2532                    setXrefPartialObject(objIdx, obj);
2533                    return true;
2534                }
2535            }
2536            else if ((ob2 = getPdfObject(((PdfDictionary)obj).get(PdfName.A))) != null) {
2537                int obj2Idx = lastXrefPartial;
2538                releaseLastXrefPartial();
2539                PdfDictionary dic = (PdfDictionary)ob2;
2540                PdfName type = (PdfName)getPdfObjectRelease(dic.get(PdfName.S));
2541                if (PdfName.GOTO.equals(type)) {
2542                    PdfObject ob3 = getPdfObjectRelease(dic.get(PdfName.D));
2543                    if (ob3 != null) {
2544                        if (ob3.isName())
2545                            name = PdfName.decodeName(ob3.toString());
2546                        else if (ob3.isString())
2547                            name = ob3.toString();
2548                    }
2549                    PdfArray dest = (PdfArray)names.get(name);
2550                    if (dest != null) {
2551                        dic.put(PdfName.D, dest);
2552                        setXrefPartialObject(obj2Idx, ob2);
2553                        setXrefPartialObject(objIdx, obj);
2554                        return true;
2555                    }
2556                }
2557            }
2558        }
2559        return false;
2560    }
2561
2562    /**
2563     * Removes all the fields from the document.
2564     */

2565    public void removeFields() {
2566        pageRefs.resetReleasePage();
2567        for (int k = 1; k <= pageRefs.size(); ++k) {
2568            PdfDictionary page = pageRefs.getPageN(k);
2569            PdfArray annots = (PdfArray)getPdfObject(page.get(PdfName.ANNOTS));
2570            if (annots == null) {
2571                pageRefs.releasePage(k);
2572                continue;
2573            }
2574            ArrayList JavaDoc arr = annots.getArrayList();
2575            for (int j = 0; j < arr.size(); ++j) {
2576                PdfDictionary annot = (PdfDictionary)getPdfObjectRelease((PdfObject)arr.get(j));
2577                if (PdfName.WIDGET.equals(annot.get(PdfName.SUBTYPE)))
2578                    arr.remove(j--);
2579            }
2580            if (arr.isEmpty())
2581                page.remove(PdfName.ANNOTS);
2582            else
2583                pageRefs.releasePage(k);
2584        }
2585        catalog.remove(PdfName.ACROFORM);
2586        pageRefs.resetReleasePage();
2587    }
2588
2589    /**
2590     * Removes all the annotations and fields from the document.
2591     */

2592    public void removeAnnotations() {
2593        pageRefs.resetReleasePage();
2594        for (int k = 1; k <= pageRefs.size(); ++k) {
2595            PdfDictionary page = pageRefs.getPageN(k);
2596            if (page.get(PdfName.ANNOTS) == null)
2597                pageRefs.releasePage(k);
2598            else
2599                page.remove(PdfName.ANNOTS);
2600        }
2601        catalog.remove(PdfName.ACROFORM);
2602        pageRefs.resetReleasePage();
2603    }
2604
2605    private void iterateBookmarks(PdfObject outlineRef, HashMap JavaDoc names) {
2606        while (outlineRef != null) {
2607            replaceNamedDestination(outlineRef, names);
2608            PdfDictionary outline = (PdfDictionary)getPdfObjectRelease(outlineRef);
2609            PdfObject first = outline.get(PdfName.FIRST);
2610            if (first != null) {
2611                iterateBookmarks(first, names);
2612            }
2613            outlineRef = outline.get(PdfName.NEXT);
2614        }
2615    }
2616
2617    /** Replaces all the local named links with the actual destinations. */
2618    public void consolidateNamedDestinations() {
2619        if (consolidateNamedDestinations)
2620            return;
2621        consolidateNamedDestinations = true;
2622        HashMap JavaDoc names = getNamedDestination();
2623        if (names.isEmpty())
2624            return;
2625        for (int k = 1; k <= pageRefs.size(); ++k) {
2626            PdfDictionary page = pageRefs.getPageN(k);
2627            PdfObject annotsRef;
2628            PdfArray annots = (PdfArray)getPdfObject(annotsRef = page.get(PdfName.ANNOTS));
2629            int annotIdx = lastXrefPartial;
2630            releaseLastXrefPartial();
2631            if (annots == null) {
2632                pageRefs.releasePage(k);
2633                continue;
2634            }
2635            ArrayList JavaDoc list = annots.getArrayList();
2636            boolean commitAnnots = false;
2637            for (int an = 0; an < list.size(); ++an) {
2638                PdfObject objRef = (PdfObject)list.get(an);
2639                if (replaceNamedDestination(objRef, names) && !objRef.isIndirect())
2640                    commitAnnots = true;
2641            }
2642            if (commitAnnots)
2643                setXrefPartialObject(annotIdx, annots);
2644            if (!commitAnnots || annotsRef.isIndirect())
2645                pageRefs.releasePage(k);
2646        }
2647        PdfDictionary outlines = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.OUTLINES));
2648        if (outlines == null)
2649            return;
2650        iterateBookmarks(outlines.get(PdfName.FIRST), names);
2651    }
2652
2653    protected static PdfDictionary duplicatePdfDictionary(PdfDictionary original, PdfDictionary copy, PdfReader newReader) {
2654        if (copy == null)
2655            copy = new PdfDictionary();
2656        for (Iterator JavaDoc it = original.getKeys().iterator(); it.hasNext();) {
2657            PdfName key = (PdfName)it.next();
2658            copy.put(key, duplicatePdfObject(original.get(key), newReader));
2659        }
2660        return copy;
2661    }
2662
2663    protected static PdfObject duplicatePdfObject(PdfObject original, PdfReader newReader) {
2664        if (original == null)
2665            return null;
2666        switch (original.type()) {
2667            case PdfObject.DICTIONARY: {
2668                return duplicatePdfDictionary((PdfDictionary)original, null, newReader);
2669            }
2670            case PdfObject.STREAM: {
2671                PRStream org = (PRStream)original;
2672                PRStream stream = new PRStream(org, null, newReader);
2673                duplicatePdfDictionary(org, stream, newReader);
2674                return stream;
2675            }
2676            case PdfObject.ARRAY: {
2677                ArrayList JavaDoc list = ((PdfArray)original).getArrayList();
2678                PdfArray arr = new PdfArray();
2679                for (Iterator JavaDoc it = list.iterator(); it.hasNext();) {
2680                    arr.add(duplicatePdfObject((PdfObject)it.next(), newReader));
2681                }
2682                return arr;
2683            }
2684            case PdfObject.INDIRECT: {
2685                PRIndirectReference org = (PRIndirectReference)original;
2686                return new PRIndirectReference(newReader, org.getNumber(), org.getGeneration());
2687            }
2688            default:
2689                return original;
2690        }
2691    }
2692
2693    /**
2694     * Closes the reader
2695     */

2696    public void close() {
2697        if (!partial)
2698            return;
2699        try {
2700            tokens.close();
2701        }
2702        catch (IOException JavaDoc e) {
2703            throw new ExceptionConverter(e);
2704        }
2705    }
2706
2707    protected void removeUnusedNode(PdfObject obj, boolean hits[]) {
2708        Stack JavaDoc state = new Stack JavaDoc();
2709        state.push(obj);
2710        while (!state.empty()) {
2711            Object JavaDoc current = state.pop();
2712            if (current == null)
2713                continue;
2714            ArrayList JavaDoc ar = null;
2715            PdfDictionary dic = null;
2716            PdfName[] keys = null;
2717            Object JavaDoc[] objs = null;
2718            int idx = 0;
2719            if (current instanceof PdfObject) {
2720                obj = (PdfObject)current;
2721                switch (obj.type()) {
2722                    case PdfObject.DICTIONARY:
2723                    case PdfObject.STREAM:
2724                        dic = (PdfDictionary)obj;
2725                        keys = new PdfName[dic.size()];
2726                        dic.getKeys().toArray(keys);
2727                        break;
2728                    case PdfObject.ARRAY:
2729                         ar = ((PdfArray)obj).getArrayList();
2730                         break;
2731                    case PdfObject.INDIRECT:
2732                        PRIndirectReference ref = (PRIndirectReference)obj;
2733                        int num = ref.getNumber();
2734                        if (!hits[num]) {
2735                            hits[num] = true;
2736                            state.push(getPdfObjectRelease(ref));
2737                        }
2738                        continue;
2739                    default:
2740                        continue;
2741                }
2742            }
2743            else {
2744                objs = (Object JavaDoc[])current;
2745                if (objs[0] instanceof ArrayList JavaDoc) {
2746                    ar = (ArrayList JavaDoc)objs[0];
2747                    idx = ((Integer JavaDoc)objs[1]).intValue();
2748                }
2749                else {
2750                    keys = (PdfName[])objs[0];
2751                    dic = (PdfDictionary)objs[1];
2752                    idx = ((Integer JavaDoc)objs[2]).intValue();
2753                }
2754            }
2755            if (ar != null) {
2756                for (int k = idx; k < ar.size(); ++k) {
2757                    PdfObject v = (PdfObject)ar.get(k);
2758                    if (v.isIndirect()) {
2759                        int num = ((PRIndirectReference)v).getNumber();
2760                        if (num >= xrefObj.size() || (!partial && xrefObj.get(num) == null)) {
2761                            ar.set(k, PdfNull.PDFNULL);
2762                            continue;
2763                        }
2764                    }
2765                    if (objs == null)
2766                        state.push(new Object JavaDoc[]{ar, new Integer JavaDoc(k + 1)});
2767                    else {
2768                        objs[1] = new Integer JavaDoc(k + 1);
2769                        state.push(objs);
2770                    }
2771                    state.push(v);
2772                    break;
2773                }
2774            }
2775            else {
2776                for (int k = idx; k < keys.length; ++k) {
2777                    PdfName key = keys[k];
2778                    PdfObject v = dic.get(key);
2779                    if (v.isIndirect()) {
2780                        int num = ((PRIndirectReference)v).getNumber();
2781                        if (num >= xrefObj.size() || (!partial && xrefObj.get(num) == null)) {
2782                            dic.put(key, PdfNull.PDFNULL);
2783                            continue;
2784                        }
2785                    }
2786                    if (objs == null)
2787                        state.push(new Object JavaDoc[]{keys, dic, new Integer JavaDoc(k + 1)});
2788                    else {
2789                        objs[2] = new Integer JavaDoc(k + 1);
2790                        state.push(objs);
2791                    }
2792                    state.push(v);
2793                    break;
2794                }
2795            }
2796        }
2797    }
2798        
2799    /** Removes all the unreachable objects.
2800     * @return the number of indirect objects removed
2801     */

2802    public int removeUnusedObjects() {
2803        boolean hits[] = new boolean[xrefObj.size()];
2804        removeUnusedNode(trailer, hits);
2805        int total = 0;
2806        if (partial) {
2807            for (int k = 1; k < hits.length; ++k) {
2808                if (!hits[k]) {
2809                    xref[k * 2] = -1;
2810                    xref[k * 2 + 1] = 0;
2811                    xrefObj.set(k, null);
2812                    ++total;
2813                }
2814            }
2815        }
2816        else {
2817            for (int k = 1; k < hits.length; ++k) {
2818                if (!hits[k]) {
2819                    xrefObj.set(k, null);
2820                    ++total;
2821                }
2822            }
2823        }
2824        return total;
2825    }
2826
2827    /** Gets a read-only version of <CODE>AcroFields</CODE>.
2828     * @return a read-only version of <CODE>AcroFields</CODE>
2829     */

2830    public AcroFields getAcroFields() {
2831        return new AcroFields(this, null);
2832    }
2833
2834    /**
2835     * Gets the global document JavaScript.
2836     * @param file the document file
2837     * @throws IOException on error
2838     * @return the global document JavaScript
2839     */

2840    public String JavaDoc getJavaScript(RandomAccessFileOrArray file) throws IOException JavaDoc {
2841        PdfDictionary names = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.NAMES));
2842        if (names == null)
2843            return null;
2844        PdfDictionary js = (PdfDictionary)getPdfObjectRelease(names.get(PdfName.JAVASCRIPT));
2845        if (js == null)
2846            return null;
2847        HashMap JavaDoc jscript = PdfNameTree.readTree(js);
2848        String JavaDoc sortedNames[] = new String JavaDoc[jscript.size()];
2849        sortedNames = (String JavaDoc[])jscript.keySet().toArray(sortedNames);
2850        Arrays.sort(sortedNames);
2851        StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
2852        for (int k = 0; k < sortedNames.length; ++k) {
2853            PdfDictionary j = (PdfDictionary)getPdfObjectRelease((PdfIndirectReference)jscript.get(sortedNames[k]));
2854            if (j == null)
2855                continue;
2856            PdfObject obj = getPdfObjectRelease(j.get(PdfName.JS));
2857            if (obj != null) {
2858                if (obj.isString())
2859                    buf.append(((PdfString)obj).toUnicodeString()).append('\n');
2860                else if (obj.isStream()) {
2861                    byte bytes[] = getStreamBytes((PRStream)obj, file);
2862                    if (bytes.length >= 2 && bytes[0] == (byte)254 && bytes[1] == (byte)255)
2863                        buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_UNICODE));
2864                    else
2865                        buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_PDFDOCENCODING));
2866                    buf.append('\n');
2867                }
2868            }
2869        }
2870        return buf.toString();
2871    }
2872
2873    /**
2874     * Gets the global document JavaScript.
2875     * @throws IOException on error
2876     * @return the global document JavaScript
2877     */

2878    public String JavaDoc getJavaScript() throws IOException JavaDoc {
2879        RandomAccessFileOrArray rf = getSafeFile();
2880        try {
2881            rf.reOpen();
2882            return getJavaScript(rf);
2883        }
2884        finally {
2885            try{rf.close();}catch(Exception JavaDoc e){}
2886        }
2887    }
2888
2889    /**
2890     * Selects the pages to keep in the document. The pages are described as
2891     * ranges. The page ordering can be changed but
2892     * no page repetitions are allowed. Note that it may be very slow in partial mode.
2893     * @param ranges the comma separated ranges as described in {@link SequenceList}
2894     */

2895    public void selectPages(String JavaDoc ranges) {
2896        selectPages(SequenceList.expand(ranges, getNumberOfPages()));
2897    }
2898
2899    /**
2900     * Selects the pages to keep in the document. The pages are described as a
2901     * <CODE>List</CODE> of <CODE>Integer</CODE>. The page ordering can be changed but
2902     * no page repetitions are allowed. Note that it may be very slow in partial mode.
2903     * @param pagesToKeep the pages to keep in the document
2904     */

2905    public void selectPages(List JavaDoc pagesToKeep) {
2906        pageRefs.selectPages(pagesToKeep);
2907        removeUnusedObjects();
2908    }
2909
2910    /** Sets the viewer preferences as the sum of several constants.
2911     * @param preferences the viewer preferences
2912     * @see PdfViewerPreferences#setViewerPreferences
2913     */

2914    public void setViewerPreferences(int preferences) {
2915        this.viewerPreferences.setViewerPreferences(preferences);
2916        setViewerPreferences(this.viewerPreferences);
2917    }
2918    
2919    /** Adds a viewer preference
2920     * @param key a key for a viewer preference
2921     * @param value a value for the viewer preference
2922     * @see PdfViewerPreferences#addViewerPreference
2923     */

2924    
2925    public void addViewerPreference(PdfName key, PdfObject value) {
2926        this.viewerPreferences.addViewerPreference(key, value);
2927        setViewerPreferences(this.viewerPreferences);
2928    }
2929    
2930    void setViewerPreferences(PdfViewerPreferencesImp vp) {
2931        vp.addToCatalog(catalog);
2932    }
2933
2934    /**
2935     * @return an int that contains the Viewer Preferences.
2936     */

2937    public int getSimpleViewerPreferences() {
2938        return PdfViewerPreferencesImp.getViewerPreferences(catalog).getPageLayoutAndMode();
2939    }
2940
2941    /**
2942     * Getter for property appendable.
2943     * @return Value of property appendable.
2944     */

2945    public boolean isAppendable() {
2946        return this.appendable;
2947    }
2948
2949    /**
2950     * Setter for property appendable.
2951     * @param appendable New value of property appendable.
2952     */

2953    public void setAppendable(boolean appendable) {
2954        this.appendable = appendable;
2955        if (appendable)
2956            getPdfObject(trailer.get(PdfName.ROOT));
2957    }
2958
2959    /**
2960     * Getter for property newXrefType.
2961     * @return Value of property newXrefType.
2962     */

2963    public boolean isNewXrefType() {
2964        return newXrefType;
2965    }
2966
2967    /**
2968     * Getter for property fileLength.
2969     * @return Value of property fileLength.
2970     */

2971    public int getFileLength() {
2972        return fileLength;
2973    }
2974
2975    /**
2976     * Getter for property hybridXref.
2977     * @return Value of property hybridXref.
2978     */

2979    public boolean isHybridXref() {
2980        return hybridXref;
2981    }
2982
2983    static class PageRefs {
2984        private PdfReader reader;
2985        private IntHashtable refsp;
2986        private ArrayList JavaDoc refsn;
2987        private ArrayList JavaDoc pageInh;
2988        private int lastPageRead = -1;
2989        private int sizep;
2990
2991        private PageRefs(PdfReader reader) throws IOException JavaDoc {
2992            this.reader = reader;
2993            if (reader.partial) {
2994                refsp = new IntHashtable();
2995                PdfNumber npages = (PdfNumber)PdfReader.getPdfObjectRelease(reader.rootPages.get(PdfName.COUNT));
2996                sizep = npages.intValue();
2997            }
2998            else {
2999                readPages();
3000            }
3001        }
3002
3003        PageRefs(PageRefs other, PdfReader reader) {
3004            this.reader = reader;
3005            this.sizep = other.sizep;
3006            if (other.refsn != null) {
3007                refsn = new ArrayList JavaDoc(other.refsn);
3008                for (int k = 0; k < refsn.size(); ++k) {
3009                    refsn.set(k, duplicatePdfObject((PdfObject)refsn.get(k), reader));
3010                }
3011            }
3012            else
3013                this.refsp = (IntHashtable)other.refsp.clone();
3014        }
3015
3016        int size() {
3017            if (refsn != null)
3018                return refsn.size();
3019            else
3020                return sizep;
3021        }
3022
3023        void readPages() throws IOException JavaDoc {
3024            if (refsn != null)
3025                return;
3026            refsp = null;
3027            refsn = new ArrayList JavaDoc();
3028            pageInh = new ArrayList JavaDoc();
3029            iteratePages((PRIndirectReference)reader.catalog.get(PdfName.PAGES));
3030            pageInh = null;
3031            reader.rootPages.put(PdfName.COUNT, new PdfNumber(refsn.size()));
3032        }
3033
3034        void reReadPages() throws IOException JavaDoc {
3035            refsn = null;
3036            readPages();
3037        }
3038
3039        /** Gets the dictionary that represents a page.
3040         * @param pageNum the page number. 1 is the first
3041         * @return the page dictionary
3042         */

3043        public PdfDictionary getPageN(int pageNum) {
3044            PRIndirectReference ref = getPageOrigRef(pageNum);
3045            return (PdfDictionary)PdfReader.getPdfObject(ref);
3046        }
3047
3048        /**
3049         * @param pageNum
3050         * @return a dictionary object
3051         */

3052        public PdfDictionary getPageNRelease(int pageNum) {
3053            PdfDictionary page = getPageN(pageNum);
3054            releasePage(pageNum);
3055            return page;
3056        }
3057
3058        /**
3059         * @param pageNum
3060         * @return an indirect reference
3061         */

3062        public PRIndirectReference getPageOrigRefRelease(int pageNum) {
3063            PRIndirectReference ref = getPageOrigRef(pageNum);
3064            releasePage(pageNum);
3065            return ref;
3066        }
3067
3068        /** Gets the page reference to this page.
3069         * @param pageNum the page number. 1 is the first
3070         * @return the page reference
3071         */

3072        public PRIndirectReference getPageOrigRef(int pageNum) {
3073            try {
3074                --pageNum;
3075                if (pageNum < 0 || pageNum >= size())
3076                    return null;
3077                if (refsn != null)
3078                    return (PRIndirectReference)refsn.get(pageNum);
3079                else {
3080                    int n = refsp.get(pageNum);
3081                    if (n == 0) {
3082                        PRIndirectReference ref = getSinglePage(pageNum);
3083                        if (reader.lastXrefPartial == -1)
3084                            lastPageRead = -1;
3085                        else
3086                            lastPageRead = pageNum;
3087                        reader.lastXrefPartial = -1;
3088                        refsp.put(pageNum, ref.getNumber());
3089                        return ref;
3090                    }
3091                    else {
3092                        if (lastPageRead != pageNum)
3093                            lastPageRead = -1;
3094                        return new PRIndirectReference(reader, n);
3095                    }
3096                }
3097            }
3098            catch (Exception JavaDoc e) {
3099                throw new ExceptionConverter(e);
3100            }
3101        }
3102
3103        /**
3104         * @param pageNum
3105         */

3106        public void releasePage(int pageNum) {
3107            if (refsp == null)
3108                return;
3109            --pageNum;
3110            if (pageNum < 0 || pageNum >= size())
3111                return;
3112            if (pageNum != lastPageRead)
3113                return;
3114            lastPageRead = -1;
3115            reader.lastXrefPartial = refsp.get(pageNum);
3116            reader.releaseLastXrefPartial();
3117            refsp.remove(pageNum);
3118        }
3119
3120        /**
3121         *
3122         */

3123        public void resetReleasePage() {
3124            if (refsp == null)
3125                return;
3126            lastPageRead = -1;
3127        }
3128
3129        void insertPage(int pageNum, PRIndirectReference ref) {
3130            --pageNum;
3131            if (refsn != null) {
3132                if (pageNum >= refsn.size())
3133                    refsn.add(ref);
3134                else
3135                    refsn.add(pageNum, ref);
3136            }
3137            else {
3138                ++sizep;
3139                lastPageRead = -1;
3140                if (pageNum >= size()) {
3141                    refsp.put(size(), ref.getNumber());
3142                }
3143                else {
3144                    IntHashtable refs2 = new IntHashtable((refsp.size() + 1) * 2);
3145                    for (Iterator JavaDoc it = refsp.getEntryIterator(); it.hasNext();) {
3146                        IntHashtable.Entry entry = (IntHashtable.Entry)it.next();
3147                        int p = entry.getKey();
3148                        refs2.put(p >= pageNum ? p + 1 : p, entry.getValue());
3149                    }
3150                    refs2.put(pageNum, ref.getNumber());
3151                    refsp = refs2;
3152                }
3153            }
3154        }
3155
3156        private void pushPageAttributes(PdfDictionary nodePages) {
3157            PdfDictionary dic = new PdfDictionary();
3158            if (!pageInh.isEmpty()) {
3159                dic.putAll((PdfDictionary)pageInh.get(pageInh.size() - 1));
3160            }
3161            for (int k = 0; k < pageInhCandidates.length; ++k) {
3162                PdfObject obj = nodePages.get(pageInhCandidates[k]);
3163                if (obj != null)
3164                    dic.put(pageInhCandidates[k], obj);
3165            }
3166            pageInh.add(dic);
3167        }
3168
3169        private void popPageAttributes() {
3170            pageInh.remove(pageInh.size() - 1);
3171        }
3172
3173        private void iteratePages(PRIndirectReference rpage) throws IOException JavaDoc {
3174            PdfDictionary page = (PdfDictionary)getPdfObject(rpage);
3175            PdfArray kidsPR = (PdfArray)getPdfObject(page.get(PdfName.KIDS));
3176            if (kidsPR == null) {
3177                page.put(PdfName.TYPE, PdfName.PAGE);
3178                PdfDictionary dic = (PdfDictionary)pageInh.get(pageInh.size() - 1);
3179                PdfName key;
3180                for (Iterator JavaDoc i = dic.getKeys().iterator(); i.hasNext();) {
3181                    key = (PdfName)i.next();
3182                    if (page.get(key) == null)
3183                        page.put(key, dic.get(key));
3184                }
3185                if (page.get(PdfName.MEDIABOX) == null) {
3186                    PdfArray arr = new PdfArray(new float[]{0,0,PageSize.LETTER.getRight(),PageSize.LETTER.getTop()});
3187                    page.put(PdfName.MEDIABOX, arr);
3188                }
3189                refsn.add(rpage);
3190            }
3191            else {
3192                page.put(PdfName.TYPE, PdfName.PAGES);
3193                pushPageAttributes(page);
3194                ArrayList JavaDoc kids = kidsPR.getArrayList();
3195                for (int k = 0; k < kids.size(); ++k){
3196                    PdfObject obj = (PdfObject)kids.get(k);
3197                    if (!obj.isIndirect()) {
3198                        while (k < kids.size())
3199                            kids.remove(k);
3200                        break;
3201                    }
3202                    iteratePages((PRIndirectReference)obj);
3203                }
3204                popPageAttributes();
3205            }
3206        }
3207
3208        protected PRIndirectReference getSinglePage(int n) {
3209            PdfDictionary acc = new PdfDictionary();
3210            PdfDictionary top = reader.rootPages;
3211            int base = 0;
3212            while (true) {
3213                for (int k = 0; k < pageInhCandidates.length; ++k) {
3214                    PdfObject obj = top.get(pageInhCandidates[k]);
3215                    if (obj != null)
3216                        acc.put(pageInhCandidates[k], obj);
3217                }
3218                PdfArray kids = (PdfArray)PdfReader.getPdfObjectRelease(top.get(PdfName.KIDS));
3219                for (Iterator JavaDoc it = kids.listIterator(); it.hasNext();) {
3220                    PRIndirectReference ref = (PRIndirectReference)it.next();
3221                    PdfDictionary dic = (PdfDictionary)getPdfObject(ref);
3222                    int last = reader.lastXrefPartial;
3223                    PdfObject count = getPdfObjectRelease(dic.get(PdfName.COUNT));
3224                    reader.lastXrefPartial = last;
3225                    int acn = 1;
3226                    if (count != null && count.type() == PdfObject.NUMBER)
3227                        acn = ((PdfNumber)count).intValue();
3228                    if (n < base + acn) {
3229                        if (count == null) {
3230                            dic.mergeDifferent(acc);
3231                            return ref;
3232                        }
3233                        reader.releaseLastXrefPartial();
3234                        top = dic;
3235                        break;
3236                    }
3237                    reader.releaseLastXrefPartial();
3238                    base += acn;
3239                }
3240            }
3241        }
3242
3243        private void selectPages(List JavaDoc pagesToKeep) {
3244            IntHashtable pg = new IntHashtable();
3245            ArrayList JavaDoc finalPages = new ArrayList JavaDoc();
3246            int psize = size();
3247            for (Iterator JavaDoc it = pagesToKeep.iterator(); it.hasNext();) {
3248                Integer JavaDoc pi = (Integer JavaDoc)it.next();
3249                int p = pi.intValue();
3250                if (p >= 1 && p <= psize && pg.put(p, 1) == 0)
3251                    finalPages.add(pi);
3252            }
3253            if (reader.partial) {
3254                for (int k = 1; k <= psize; ++k) {
3255                    getPageOrigRef(k);
3256                    resetReleasePage();
3257                }
3258            }
3259            PRIndirectReference parent = (PRIndirectReference)reader.catalog.get(PdfName.PAGES);
3260            PdfDictionary topPages = (PdfDictionary)PdfReader.getPdfObject(parent);
3261            ArrayList JavaDoc newPageRefs = new ArrayList JavaDoc(finalPages.size());
3262            PdfArray kids = new PdfArray();
3263            for (int k = 0; k < finalPages.size(); ++k) {
3264                int p = ((Integer JavaDoc)finalPages.get(k)).intValue();
3265                PRIndirectReference pref = getPageOrigRef(p);
3266                resetReleasePage();
3267                kids.add(pref);
3268                newPageRefs.add(pref);
3269                getPageN(p).put(PdfName.PARENT, parent);
3270            }
3271            AcroFields af = reader.getAcroFields();
3272            boolean removeFields = (af.getFields().size() > 0);
3273            for (int k = 1; k <= psize; ++k) {
3274                if (!pg.containsKey(k)) {
3275                    if (removeFields)
3276                        af.removeFieldsFromPage(k);
3277                    PRIndirectReference pref = getPageOrigRef(k);
3278                    int nref = pref.getNumber();
3279                    reader.xrefObj.set(nref, null);
3280                    if (reader.partial) {
3281                        reader.xref[nref * 2] = -1;
3282                        reader.xref[nref * 2 + 1] = 0;
3283                    }
3284                }
3285            }
3286            topPages.put(PdfName.COUNT, new PdfNumber(finalPages.size()));
3287            topPages.put(PdfName.KIDS, kids);
3288            refsp = null;
3289            refsn = newPageRefs;
3290        }
3291    }
3292    
3293    PdfIndirectReference getCryptoRef() {
3294        if (cryptoRef == null)
3295            return null;
3296        return new PdfIndirectReference(0, cryptoRef.getNumber(), cryptoRef.getGeneration());
3297    }
3298    
3299    /**
3300     * Removes any usage rights that this PDF may have. Only Adobe can grant usage rights
3301     * and any PDF modification with iText will invalidate them. Invalidated usage rights may
3302     * confuse Acrobat and it's advisabe to remove them altogether.
3303     */

3304    public void removeUsageRights() {
3305        PdfDictionary perms = (PdfDictionary)getPdfObject(catalog.get(PdfName.PERMS));
3306        if (perms == null)
3307            return;
3308        perms.remove(PdfName.UR);
3309        perms.remove(PdfName.UR3);
3310        if (perms.size() == 0)
3311            catalog.remove(PdfName.PERMS);
3312    }
3313    
3314    /**
3315     * Gets the certification level for this document. The return values can be <code>PdfSignatureAppearance.NOT_CERTIFIED</code>,
3316     * <code>PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED</code>,
3317     * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING</code> and
3318     * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS</code>.
3319     * <p>
3320     * No signature validation is made, use the methods availabe for that in <CODE>AcroFields</CODE>.
3321     * </p>
3322     * @return gets the certification level for this document
3323     */

3324    public int getCertificationLevel() {
3325        PdfDictionary dic = (PdfDictionary)getPdfObject(catalog.get(PdfName.PERMS));
3326        if (dic == null)
3327            return PdfSignatureAppearance.NOT_CERTIFIED;
3328        dic = (PdfDictionary)getPdfObject(dic.get(PdfName.DOCMDP));
3329        if (dic == null)
3330            return PdfSignatureAppearance.NOT_CERTIFIED;
3331        PdfArray arr = (PdfArray)getPdfObject(dic.get(PdfName.REFERENCE));
3332        if (arr == null || arr.size() == 0)
3333            return PdfSignatureAppearance.NOT_CERTIFIED;
3334        dic = (PdfDictionary)getPdfObject((PdfObject)(arr.getArrayList().get(0)));
3335        if (dic == null)
3336            return PdfSignatureAppearance.NOT_CERTIFIED;
3337        dic = (PdfDictionary)getPdfObject(dic.get(PdfName.TRANSFORMPARAMS));
3338        if (dic == null)
3339            return PdfSignatureAppearance.NOT_CERTIFIED;
3340        PdfNumber p = (PdfNumber)getPdfObject(dic.get(PdfName.P));
3341        if (p == null)
3342            return PdfSignatureAppearance.NOT_CERTIFIED;
3343        return p.intValue();
3344    }
3345    
3346    /**
3347     * Checks if the document was opened with the owner password so that the end application
3348     * can decide what level of access restrictions to apply. If the document is not encrypted
3349     * it will return <CODE>true</CODE>.
3350     * @return <CODE>true</CODE> if the document was opened with the owner password or if it's not encrypted,
3351     * <CODE>false</CODE> if the document was opened with the user password
3352     */

3353    public final boolean isOpenedWithFullPermissions() {
3354        return !encrypted || ownerPasswordUsed;
3355    }
3356    
3357    public int getCryptoMode() {
3358        if (decrypt == null)
3359            return -1;
3360        else
3361            return decrypt.getCryptoMode();
3362    }
3363    
3364    public boolean isMetadataEncrypted() {
3365        if (decrypt == null)
3366            return false;
3367        else
3368            return decrypt.isMetadataEncrypted();
3369    }
3370    
3371    public byte[] computeUserPassword() {
3372        if (!encrypted || !ownerPasswordUsed) return null;
3373        return decrypt.computeUserPassword(password);
3374    }
3375}
Popular Tags