Save This Page
Home » iText-src-2.1.3 » com.lowagie » text » pdf » [javadoc | source]
    1   /*
    2    * $Id: PdfReader.java 3537 2008-07-08 08:53:00Z blowagie $
    3    *
    4    * Copyright 2001, 2002 Paulo Soares
    5    *
    6    * The contents of this file are subject to the Mozilla Public License Version 1.1
    7    * (the "License"); you may not use this file except in compliance with the License.
    8    * You may obtain a copy of the License at http://www.mozilla.org/MPL/
    9    *
   10    * Software distributed under the License is distributed on an "AS IS" basis,
   11    * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
   12    * for the specific language governing rights and limitations under the License.
   13    *
   14    * The Original Code is 'iText, a free JAVA-PDF library'.
   15    *
   16    * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
   17    * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
   18    * All Rights Reserved.
   19    * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
   20    * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
   21    *
   22    * Contributor(s): all the names of the contributors are added in the source code
   23    * where applicable.
   24    *
   25    * Alternatively, the contents of this file may be used under the terms of the
   26    * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
   27    * provisions of LGPL are applicable instead of those above.  If you wish to
   28    * allow use of your version of this file only under the terms of the LGPL
   29    * License and not to allow others to use your version of this file under
   30    * the MPL, indicate your decision by deleting the provisions above and
   31    * replace them with the notice and other provisions required by the LGPL.
   32    * If you do not delete the provisions above, a recipient may use your version
   33    * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
   34    *
   35    * This library is free software; you can redistribute it and/or modify it
   36    * under the terms of the MPL as stated above or under the terms of the GNU
   37    * Library General Public License as published by the Free Software Foundation;
   38    * either version 2 of the License, or any later version.
   39    *
   40    * This library is distributed in the hope that it will be useful, but WITHOUT
   41    * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   42    * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
   43    * details.
   44    *
   45    * If you didn't download this code from the following link, you should check if
   46    * you aren't using an obsolete version:
   47    * http://www.lowagie.com/iText/
   48    */
   49   
   50   package com.lowagie.text.pdf;
   51   
   52   import java.io.ByteArrayInputStream;
   53   import java.io.ByteArrayOutputStream;
   54   import java.io.DataInputStream;
   55   import java.io.IOException;
   56   import java.io.InputStream;
   57   import java.net.URL;
   58   import java.util.ArrayList;
   59   import java.util.Arrays;
   60   import java.util.Collections;
   61   import java.util.HashMap;
   62   import java.util.Iterator;
   63   import java.util.List;
   64   import java.util.Map;
   65   import java.util.Set;
   66   import java.util.zip.InflaterInputStream;
   67   import java.util.Stack;
   68   import java.security.Key;
   69   import java.security.MessageDigest;
   70   import java.security.cert.Certificate;
   71   
   72   import com.lowagie.text.ExceptionConverter;
   73   import com.lowagie.text.PageSize;
   74   import com.lowagie.text.Rectangle;
   75   import com.lowagie.text.pdf.interfaces.PdfViewerPreferences;
   76   import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
   77   
   78   import org.bouncycastle.cms.CMSEnvelopedData;
   79   import org.bouncycastle.cms.RecipientInformation;
   80   
   81   /** Reads a PDF document.
   82    * @author Paulo Soares (psoares@consiste.pt)
   83    * @author Kazuya Ujihara
   84    */
   85   public class PdfReader implements PdfViewerPreferences {
   86   
   87       static final PdfName pageInhCandidates[] = {
   88           PdfName.MEDIABOX, PdfName.ROTATE, PdfName.RESOURCES, PdfName.CROPBOX
   89       };
   90      
   91       static final byte endstream[] = PdfEncodings.convertToBytes("endstream", null);
   92       static final byte endobj[] = PdfEncodings.convertToBytes("endobj", null);
   93       protected PRTokeniser tokens;
   94       // Each xref pair is a position
   95       // type 0 -> -1, 0
   96       // type 1 -> offset, 0
   97       // type 2 -> index, obj num
   98       protected int xref[];
   99       protected HashMap objStmMark;
  100       protected IntHashtable objStmToOffset;
  101       protected boolean newXrefType;
  102       private ArrayList xrefObj;
  103       PdfDictionary rootPages;
  104       protected PdfDictionary trailer;
  105       protected PdfDictionary catalog;
  106       protected PageRefs pageRefs;
  107       protected PRAcroForm acroForm = null;
  108       protected boolean acroFormParsed = false;
  109       protected boolean encrypted = false;
  110       protected boolean rebuilt = false;
  111       protected int freeXref;
  112       protected boolean tampered = false;
  113       protected int lastXref;
  114       protected int eofPos;
  115       protected char pdfVersion;
  116       protected PdfEncryption decrypt;
  117       protected byte password[] = null; //added by ujihara for decryption
  118       protected Key certificateKey = null; //added by Aiken Sam for certificate decryption
  119       protected Certificate certificate = null; //added by Aiken Sam for certificate decryption
  120       protected String certificateKeyProvider = null; //added by Aiken Sam for certificate decryption
  121       private boolean ownerPasswordUsed;
  122       protected ArrayList strings = new ArrayList();
  123       protected boolean sharedStreams = true;
  124       protected boolean consolidateNamedDestinations = false;
  125       protected int rValue;
  126       protected int pValue;
  127       private int objNum;
  128       private int objGen;
  129       private int fileLength;
  130       private boolean hybridXref;
  131       private int lastXrefPartial = -1;
  132       private boolean partial;
  133       private PRIndirectReference cryptoRef;
  134   	private PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
  135       private boolean encryptionError;
  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 filename) throws IOException {
  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 filename, byte ownerPassword[]) throws IOException {
  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 {
  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 {
  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 filename, Certificate certificate, Key certificateKey, String certificateKeyProvider) throws IOException {
  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 url) throws IOException {
  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 url, byte ownerPassword[]) throws IOException {
  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 is, byte ownerPassword[]) throws IOException {
  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 is) throws IOException {
  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 {
  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(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 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 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 getInfo() {
  445           HashMap map = new HashMap();
  446           PdfDictionary info = (PdfDictionary)getPdfObject(trailer.get(PdfName.INFO));
  447           if (info == null)
  448               return map;
  449           for (Iterator 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 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 rect = box.getArrayList();
  476           float llx = ((PdfNumber)getPdfObjectRelease((PdfObject)rect.get(0))).floatValue();
  477           float lly = ((PdfNumber)getPdfObjectRelease((PdfObject)rect.get(1))).floatValue();
  478           float urx = ((PdfNumber)getPdfObjectRelease((PdfObject)rect.get(2))).floatValue();
  479           float ury = ((PdfNumber)getPdfObjectRelease((PdfObject)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 {
  485           try {
  486               fileLength = tokens.getFile().length();
  487               pdfVersion = tokens.checkPdfHeader();
  488               try {
  489                   readXref();
  490               }
  491               catch (Exception e) {
  492                   try {
  493                       rebuilt = true;
  494                       rebuildXref();
  495                       lastXref = -1;
  496                   }
  497                   catch (Exception ne) {
  498                       throw new IOException("Rebuild failed: " + ne.getMessage() + "; Original message: " + e.getMessage());
  499                   }
  500               }
  501               try {
  502                   readDocObj();
  503               }
  504               catch (Exception ne) {
  505                   if (rebuilt || encryptionError)
  506                       throw new IOException(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 e) {
  524                   // empty on purpose
  525               }
  526           }
  527       }
  528   
  529       protected void readPdfPartial() throws IOException {
  530           try {
  531               fileLength = tokens.getFile().length();
  532               pdfVersion = tokens.checkPdfHeader();
  533               try {
  534                   readXref();
  535               }
  536               catch (Exception e) {
  537                   try {
  538                       rebuilt = true;
  539                       rebuildXref();
  540                       lastXref = -1;
  541                   }
  542                   catch (Exception ne) {
  543                       throw new IOException("Rebuild failed: " + ne.getMessage() + "; Original message: " + e.getMessage());
  544                   }
  545               }
  546               readDocObjPartial();
  547               readPages();
  548           }
  549           catch (IOException e) {
  550               try{tokens.close();}catch(Exception 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 {
  567           if (encrypted)
  568               return;
  569           PdfObject encDic = trailer.get(PdfName.ENCRYPT);
  570           if (encDic == null || encDic.toString().equals("null"))
  571               return;
  572           encryptionError = true;
  573           byte[] encryptionKey = null;    	
  574           encrypted = true;
  575           PdfDictionary enc = (PdfDictionary)getPdfObject(encDic);
  576   
  577           String s;
  578           PdfObject o;
  579   
  580           PdfArray documentIDs = (PdfArray)getPdfObject(trailer.get(PdfName.ID));
  581           byte documentID[] = null;
  582           if (documentIDs != null) {
  583               o = (PdfObject)documentIDs.getArrayList().get(0);
  584               strings.remove(o);
  585               s = o.toString();
  586               documentID = com.lowagie.text.DocWriter.getISOBytes(s);
  587               if (documentIDs.size() > 1)
  588                   strings.remove(documentIDs.getArrayList().get(1));
  589           }
  590           // just in case we have a broken producer
  591           if (documentID == null)
  592               documentID = new byte[0];
  593           byte uValue[] = null;
  594           byte oValue[] = null;
  595           int cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
  596           int lengthValue = 0;  
  597           
  598           PdfObject filter = getPdfObjectRelease(enc.get(PdfName.FILTER));
  599   
  600           if (filter.equals(PdfName.STANDARD))
  601           {        	        
  602               s = enc.get(PdfName.U).toString();
  603               strings.remove(enc.get(PdfName.U));
  604               uValue = com.lowagie.text.DocWriter.getISOBytes(s);
  605               s = enc.get(PdfName.O).toString();
  606               strings.remove(enc.get(PdfName.O));
  607               oValue = com.lowagie.text.DocWriter.getISOBytes(s);
  608   
  609               o = enc.get(PdfName.R);
  610               if (!o.isNumber()) throw new IOException("Illegal R value.");
  611               rValue = ((PdfNumber)o).intValue();
  612               if (rValue != 2 && rValue != 3 && rValue != 4)
  613                   throw new IOException("Unknown encryption type (" + rValue + ")");
  614   
  615               o = enc.get(PdfName.P);
  616               if (!o.isNumber()) throw new IOException("Illegal P value.");
  617               pValue = ((PdfNumber)o).intValue();
  618      
  619               if ( rValue == 3 ){
  620                   o = enc.get(PdfName.LENGTH);
  621                   if (!o.isNumber())
  622                       throw new IOException("Illegal Length value.");
  623                   lengthValue = ( (PdfNumber) o).intValue();
  624                   if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
  625                       throw new IOException("Illegal Length value.");
  626                   cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
  627               }  else if (rValue == 4) {
  628                   PdfDictionary dic = (PdfDictionary)enc.get(PdfName.CF);
  629                   if (dic == null)
  630                       throw new IOException("/CF not found (encryption)");
  631                   dic = (PdfDictionary)dic.get(PdfName.STDCF);
  632                   if (dic == null)
  633                       throw new IOException("/StdCF not found (encryption)");
  634                   if (PdfName.V2.equals(dic.get(PdfName.CFM)))
  635                       cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
  636                   else if (PdfName.AESV2.equals(dic.get(PdfName.CFM)))
  637                       cryptoMode = PdfWriter.ENCRYPTION_AES_128;
  638                   else
  639                       throw new IOException("No compatible encryption found");
  640                   PdfObject em = enc.get(PdfName.ENCRYPTMETADATA);
  641                   if (em != null && em.toString().equals("false"))
  642                       cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
  643               } else {
  644                   cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
  645               }
  646           } else if (filter.equals(PdfName.PUBSEC)) {
  647               boolean foundRecipient = false;
  648               byte[] envelopedData = null;
  649               PdfArray recipients = null;
  650   
  651               o = enc.get(PdfName.V);
  652               if (!o.isNumber()) throw new IOException("Illegal V value.");
  653               int vValue = ((PdfNumber)o).intValue();
  654               if (vValue != 1 && vValue != 2 && vValue != 4)
  655                   throw new IOException("Unknown encryption type V = " + rValue);
  656   
  657               if ( vValue == 2 ){
  658                   o = enc.get(PdfName.LENGTH);
  659                   if (!o.isNumber())
  660                       throw new IOException("Illegal Length value.");
  661                   lengthValue = ( (PdfNumber) o).intValue();
  662                   if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
  663                       throw new IOException("Illegal Length value.");
  664                   cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;                
  665                   recipients = (PdfArray)enc.get(PdfName.RECIPIENTS);                                
  666               } else if (vValue == 4) {
  667                   PdfDictionary dic = (PdfDictionary)enc.get(PdfName.CF);
  668                   if (dic == null)
  669                       throw new IOException("/CF not found (encryption)");
  670                   dic = (PdfDictionary)dic.get(PdfName.DEFAULTCRYPTFILER);
  671                   if (dic == null)
  672                       throw new IOException("/DefaultCryptFilter not found (encryption)");
  673                   if (PdfName.V2.equals(dic.get(PdfName.CFM)))
  674                   {
  675                       cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
  676                       lengthValue = 128;
  677                   }
  678                   else if (PdfName.AESV2.equals(dic.get(PdfName.CFM)))
  679                   {
  680                       cryptoMode = PdfWriter.ENCRYPTION_AES_128;
  681                       lengthValue = 128;
  682                   }
  683                   else
  684                       throw new IOException("No compatible encryption found");
  685                   PdfObject em = dic.get(PdfName.ENCRYPTMETADATA);
  686                   if (em != null && em.toString().equals("false"))
  687                       cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
  688                   
  689                   recipients = (PdfArray)dic.get(PdfName.RECIPIENTS);                                    
  690               } else {
  691                   cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
  692                   lengthValue = 40;                
  693                   recipients = (PdfArray)enc.get(PdfName.RECIPIENTS);                
  694               }
  695   
  696               for (int i = 0; i<recipients.size(); i++)
  697               {
  698                   PdfObject recipient = (PdfObject)recipients.getArrayList().get(i);
  699                   strings.remove(recipient);
  700                   
  701                   CMSEnvelopedData data = null;
  702                   try {
  703                       data = new CMSEnvelopedData(recipient.getBytes());
  704                   
  705                       Iterator recipientCertificatesIt = 
  706                           data.getRecipientInfos().getRecipients().iterator();
  707               
  708                       while (recipientCertificatesIt.hasNext()) {
  709                           RecipientInformation recipientInfo = 
  710                               (RecipientInformation)recipientCertificatesIt.next();
  711   
  712                           if (recipientInfo.getRID().match(certificate) && !foundRecipient) {
  713       
  714                            envelopedData = recipientInfo.getContent(certificateKey, certificateKeyProvider);
  715                            foundRecipient = true;                         
  716                           }
  717                       }                        
  718                   } catch (Exception f) {
  719                       throw new ExceptionConverter(f);
  720                   }            
  721               }
  722               
  723               if(!foundRecipient || envelopedData == null)
  724               {
  725                   throw new IOException("Bad certificate and key.");
  726               }            
  727   
  728               MessageDigest md = null;
  729   
  730               try {
  731                   md = MessageDigest.getInstance("SHA-1");
  732                   md.update(envelopedData, 0, 20);
  733                   for (int i=0; i<recipients.size(); i++)
  734                   {
  735                     byte[] encodedRecipient = ((PdfObject)recipients.getArrayList().get(i)).getBytes();  
  736                     md.update(encodedRecipient);
  737                   }
  738                   if ((cryptoMode & PdfWriter.DO_NOT_ENCRYPT_METADATA) != 0)
  739                       md.update(new byte[]{(byte)255, (byte)255, (byte)255, (byte)255});
  740                   encryptionKey = md.digest();
  741                   
  742               } catch (Exception f) {
  743                   throw new ExceptionConverter(f);
  744               }
  745           }
  746   
  747   
  748           decrypt = new PdfEncryption();
  749           decrypt.setCryptoMode(cryptoMode, lengthValue);
  750   
  751           if (filter.equals(PdfName.STANDARD))
  752           {
  753               //check by owner password
  754               decrypt.setupByOwnerPassword(documentID, password, uValue, oValue, pValue);
  755               if (!equalsArray(uValue, decrypt.userKey, (rValue == 3 || rValue == 4) ? 16 : 32)) {
  756                   //check by user password
  757                   decrypt.setupByUserPassword(documentID, password, oValue, pValue);
  758                   if (!equalsArray(uValue, decrypt.userKey, (rValue == 3 || rValue == 4) ? 16 : 32)) {
  759                       throw new BadPasswordException();
  760                   }
  761               }
  762               else
  763                   ownerPasswordUsed = true;
  764           } else if (filter.equals(PdfName.PUBSEC)) {   
  765               decrypt.setupByEncryptionKey(encryptionKey, lengthValue);
  766               ownerPasswordUsed = true;
  767           }
  768                    
  769           for (int k = 0; k < strings.size(); ++k) {
  770               PdfString str = (PdfString)strings.get(k);
  771               str.decrypt(this);
  772           }
  773           
  774           if (encDic.isIndirect()) {
  775               cryptoRef = (PRIndirectReference)encDic;
  776               xrefObj.set(cryptoRef.getNumber(), null);
  777           }
  778           encryptionError = false;
  779       }
  780   
  781       /**
  782        * @param obj
  783        * @return a PdfObject
  784        */
  785       public static PdfObject getPdfObjectRelease(PdfObject obj) {
  786           PdfObject obj2 = getPdfObject(obj);
  787           releaseLastXrefPartial(obj);
  788           return obj2;
  789       }
  790   
  791   
  792       /**
  793        * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
  794        * if needed.
  795        * @param obj the <CODE>PdfObject</CODE> to read
  796        * @return the resolved <CODE>PdfObject</CODE>
  797        */
  798       public static PdfObject getPdfObject(PdfObject obj) {
  799           if (obj == null)
  800               return null;
  801           if (!obj.isIndirect())
  802               return obj;
  803           try {
  804               PRIndirectReference ref = (PRIndirectReference)obj;
  805               int idx = ref.getNumber();
  806               boolean appendable = ref.getReader().appendable;
  807               obj = ref.getReader().getPdfObject(idx);
  808               if (obj == null) {
  809                   return null;
  810               }
  811               else {
  812                   if (appendable) {
  813                       switch (obj.type()) {
  814                           case PdfObject.NULL:
  815                               obj = new PdfNull();
  816                               break;
  817                           case PdfObject.BOOLEAN:
  818                               obj = new PdfBoolean(((PdfBoolean)obj).booleanValue());
  819                               break;
  820                           case PdfObject.NAME:
  821                               obj = new PdfName(obj.getBytes());
  822                               break;
  823                       }
  824                       obj.setIndRef(ref);
  825                   }
  826                   return obj;
  827               }
  828           }
  829           catch (Exception e) {
  830               throw new ExceptionConverter(e);
  831           }
  832       }
  833   
  834       /**
  835        * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
  836        * if needed. If the reader was opened in partial mode the object will be released
  837        * to save memory.
  838        * @param obj the <CODE>PdfObject</CODE> to read
  839        * @param parent
  840        * @return a PdfObject
  841        */
  842       public static PdfObject getPdfObjectRelease(PdfObject obj, PdfObject parent) {
  843           PdfObject obj2 = getPdfObject(obj, parent);
  844           releaseLastXrefPartial(obj);
  845           return obj2;
  846       }
  847   
  848       /**
  849        * @param obj
  850        * @param parent
  851        * @return a PdfObject
  852        */
  853       public static PdfObject getPdfObject(PdfObject obj, PdfObject parent) {
  854           if (obj == null)
  855               return null;
  856           if (!obj.isIndirect()) {
  857               PRIndirectReference ref = null;
  858               if (parent != null && (ref = parent.getIndRef()) != null && ref.getReader().isAppendable()) {
  859                   switch (obj.type()) {
  860                       case PdfObject.NULL:
  861                           obj = new PdfNull();
  862                           break;
  863                       case PdfObject.BOOLEAN:
  864                           obj = new PdfBoolean(((PdfBoolean)obj).booleanValue());
  865                           break;
  866                       case PdfObject.NAME:
  867                           obj = new PdfName(obj.getBytes());
  868                           break;
  869                   }
  870                   obj.setIndRef(ref);
  871               }
  872               return obj;
  873           }
  874           return getPdfObject(obj);
  875       }
  876   
  877       /**
  878        * @param idx
  879        * @return a PdfObject
  880        */
  881       public PdfObject getPdfObjectRelease(int idx) {
  882           PdfObject obj = getPdfObject(idx);
  883           releaseLastXrefPartial();
  884           return obj;
  885       }
  886   
  887       /**
  888        * @param idx
  889        * @return aPdfObject
  890        */
  891       public PdfObject getPdfObject(int idx) {
  892           try {
  893               lastXrefPartial = -1;
  894               if (idx < 0 || idx >= xrefObj.size())
  895                   return null;
  896               PdfObject obj = (PdfObject)xrefObj.get(idx);
  897               if (!partial || obj != null)
  898                   return obj;
  899               if (idx * 2 >= xref.length)
  900                   return null;
  901               obj = readSingleObject(idx);
  902               lastXrefPartial = -1;
  903               if (obj != null)
  904                   lastXrefPartial = idx;
  905               return obj;
  906           }
  907           catch (Exception e) {
  908               throw new ExceptionConverter(e);
  909           }
  910       }
  911   
  912       /**
  913        *
  914        */
  915       public void resetLastXrefPartial() {
  916           lastXrefPartial = -1;
  917       }
  918   
  919       /**
  920        *
  921        */
  922       public void releaseLastXrefPartial() {
  923           if (partial && lastXrefPartial != -1) {
  924               xrefObj.set(lastXrefPartial, null);
  925               lastXrefPartial = -1;
  926           }
  927       }
  928   
  929       /**
  930        * @param obj
  931        */
  932       public static void releaseLastXrefPartial(PdfObject obj) {
  933           if (obj == null)
  934               return;
  935           if (!obj.isIndirect())
  936               return;
  937           PRIndirectReference ref = (PRIndirectReference)obj;
  938           PdfReader reader = ref.getReader();
  939           if (reader.partial && reader.lastXrefPartial != -1 && reader.lastXrefPartial == ref.getNumber()) {
  940               reader.xrefObj.set(reader.lastXrefPartial, null);
  941           }
  942           reader.lastXrefPartial = -1;
  943       }
  944   
  945       private void setXrefPartialObject(int idx, PdfObject obj) {
  946           if (!partial || idx < 0)
  947               return;
  948           xrefObj.set(idx, obj);
  949       }
  950   
  951       /**
  952        * @param obj
  953        * @return an indirect reference
  954        */
  955       public PRIndirectReference addPdfObject(PdfObject obj) {
  956           xrefObj.add(obj);
  957           return new PRIndirectReference(this, xrefObj.size() - 1);
  958       }
  959   
  960       protected void readPages() throws IOException {
  961           catalog = (PdfDictionary)getPdfObject(trailer.get(PdfName.ROOT));
  962           rootPages = (PdfDictionary)getPdfObject(catalog.get(PdfName.PAGES));
  963           pageRefs = new PageRefs(this);
  964       }
  965   
  966       protected void readDocObjPartial() throws IOException {
  967           xrefObj = new ArrayList(xref.length / 2);
  968           xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
  969           readDecryptedDocObj();
  970           if (objStmToOffset != null) {
  971               int keys[] = objStmToOffset.getKeys();
  972               for (int k = 0; k < keys.length; ++k) {
  973                   int n = keys[k];
  974                   objStmToOffset.put(n, xref[n * 2]);
  975                   xref[n * 2] = -1;
  976               }
  977           }
  978       }
  979   
  980       protected PdfObject readSingleObject(int k) throws IOException {
  981           strings.clear();
  982           int k2 = k * 2;
  983           int pos = xref[k2];
  984           if (pos < 0)
  985               return null;
  986           if (xref[k2 + 1] > 0)
  987               pos = objStmToOffset.get(xref[k2 + 1]);
  988           if (pos == 0)
  989               return null;
  990           tokens.seek(pos);
  991           tokens.nextValidToken();
  992           if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
  993               tokens.throwError("Invalid object number.");
  994           objNum = tokens.intValue();
  995           tokens.nextValidToken();
  996           if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
  997               tokens.throwError("Invalid generation number.");
  998           objGen = tokens.intValue();
  999           tokens.nextValidToken();
 1000           if (!tokens.getStringValue().equals("obj"))
 1001               tokens.throwError("Token 'obj' expected.");
 1002           PdfObject obj;
 1003           try {
 1004               obj = readPRObject();
 1005               for (int j = 0; j < strings.size(); ++j) {
 1006                   PdfString str = (PdfString)strings.get(j);
 1007                   str.decrypt(this);
 1008               }
 1009               if (obj.isStream()) {
 1010                   checkPRStreamLength((PRStream)obj);
 1011               }
 1012           }
 1013           catch (Exception e) {
 1014               obj = null;
 1015           }
 1016           if (xref[k2 + 1] > 0) {
 1017               obj = readOneObjStm((PRStream)obj, xref[k2]);
 1018           }
 1019           xrefObj.set(k, obj);
 1020           return obj;
 1021       }
 1022   
 1023       protected PdfObject readOneObjStm(PRStream stream, int idx) throws IOException {
 1024           int first = ((PdfNumber)getPdfObject(stream.get(PdfName.FIRST))).intValue();
 1025           byte b[] = getStreamBytes(stream, tokens.getFile());
 1026           PRTokeniser saveTokens = tokens;
 1027           tokens = new PRTokeniser(b);
 1028           try {
 1029               int address = 0;
 1030               boolean ok = true;
 1031               ++idx;
 1032               for (int k = 0; k < idx; ++k) {
 1033                   ok = tokens.nextToken();
 1034                   if (!ok)
 1035                       break;
 1036                   if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
 1037                       ok = false;
 1038                       break;
 1039                   }
 1040                   ok = tokens.nextToken();
 1041                   if (!ok)
 1042                       break;
 1043                   if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
 1044                       ok = false;
 1045                       break;
 1046                   }
 1047                   address = tokens.intValue() + first;
 1048               }
 1049               if (!ok)
 1050                   throw new IOException("Error reading ObjStm");
 1051               tokens.seek(address);
 1052               return readPRObject();
 1053           }
 1054           finally {
 1055               tokens = saveTokens;
 1056           }
 1057       }
 1058   
 1059       /**
 1060        * @return the percentage of the cross reference table that has been read
 1061        */
 1062       public double dumpPerc() {
 1063           int total = 0;
 1064           for (int k = 0; k < xrefObj.size(); ++k) {
 1065               if (xrefObj.get(k) != null)
 1066                   ++total;
 1067           }
 1068           return (total * 100.0 / xrefObj.size());
 1069       }
 1070   
 1071       protected void readDocObj() throws IOException {
 1072           ArrayList streams = new ArrayList();
 1073           xrefObj = new ArrayList(xref.length / 2);
 1074           xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
 1075           for (int k = 2; k < xref.length; k += 2) {
 1076               int pos = xref[k];
 1077               if (pos <= 0 || xref[k + 1] > 0)
 1078                   continue;
 1079               tokens.seek(pos);
 1080               tokens.nextValidToken();
 1081               if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
 1082                   tokens.throwError("Invalid object number.");
 1083               objNum = tokens.intValue();
 1084               tokens.nextValidToken();
 1085               if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
 1086                   tokens.throwError("Invalid generation number.");
 1087               objGen = tokens.intValue();
 1088               tokens.nextValidToken();
 1089               if (!tokens.getStringValue().equals("obj"))
 1090                   tokens.throwError("Token 'obj' expected.");
 1091               PdfObject obj;
 1092               try {
 1093                   obj = readPRObject();
 1094                   if (obj.isStream()) {
 1095                       streams.add(obj);
 1096                   }
 1097               }
 1098               catch (Exception e) {
 1099                   obj = null;
 1100               }
 1101               xrefObj.set(k / 2, obj);
 1102           }
 1103           for (int k = 0; k < streams.size(); ++k) {
 1104               checkPRStreamLength((PRStream)streams.get(k));
 1105           }
 1106           readDecryptedDocObj();
 1107           if (objStmMark != null) {
 1108               for (Iterator i = objStmMark.entrySet().iterator(); i.hasNext();) {
 1109                   Map.Entry entry = (Map.Entry)i.next();
 1110                   int n = ((Integer)entry.getKey()).intValue();
 1111                   IntHashtable h = (IntHashtable)entry.getValue();
 1112                   readObjStm((PRStream)xrefObj.get(n), h);
 1113                   xrefObj.set(n, null);
 1114               }
 1115               objStmMark = null;
 1116           }
 1117           xref = null;
 1118       }
 1119   
 1120       private void checkPRStreamLength(PRStream stream) throws IOException {
 1121           int fileLength = tokens.length();
 1122           int start = stream.getOffset();
 1123           boolean calc = false;
 1124           int streamLength = 0;
 1125           PdfObject obj = getPdfObjectRelease(stream.get(PdfName.LENGTH));
 1126           if (obj != null && obj.type() == PdfObject.NUMBER) {
 1127               streamLength = ((PdfNumber)obj).intValue();
 1128               if (streamLength + start > fileLength - 20)
 1129                   calc = true;
 1130               else {
 1131                   tokens.seek(start + streamLength);
 1132                   String line = tokens.readString(20);
 1133                   if (!line.startsWith("\nendstream") &&
 1134                   !line.startsWith("\r\nendstream") &&
 1135                   !line.startsWith("\rendstream") &&
 1136                   !line.startsWith("endstream"))
 1137                       calc = true;
 1138               }
 1139           }
 1140           else
 1141               calc = true;
 1142           if (calc) {
 1143               byte tline[] = new byte[16];
 1144               tokens.seek(start);
 1145               while (true) {
 1146                   int pos = tokens.getFilePointer();
 1147                   if (!tokens.readLineSegment(tline))
 1148                       break;
 1149                   if (equalsn(tline, endstream)) {
 1150                       streamLength = pos - start;
 1151                       break;
 1152                   }
 1153                   if (equalsn(tline, endobj)) {
 1154                       tokens.seek(pos - 16);
 1155                       String s = tokens.readString(16);
 1156                       int index = s.indexOf("endstream");
 1157                       if (index >= 0)
 1158                           pos = pos - 16 + index;
 1159                       streamLength = pos - start;
 1160                       break;
 1161                   }
 1162               }
 1163           }
 1164           stream.setLength(streamLength);
 1165       }
 1166   
 1167       protected void readObjStm(PRStream stream, IntHashtable map) throws IOException {
 1168           int first = ((PdfNumber)getPdfObject(stream.get(PdfName.FIRST))).intValue();
 1169           int n = ((PdfNumber)getPdfObject(stream.get(PdfName.N))).intValue();
 1170           byte b[] = getStreamBytes(stream, tokens.getFile());
 1171           PRTokeniser saveTokens = tokens;
 1172           tokens = new PRTokeniser(b);
 1173           try {
 1174               int address[] = new int[n];
 1175               int objNumber[] = new int[n];
 1176               boolean ok = true;
 1177               for (int k = 0; k < n; ++k) {
 1178                   ok = tokens.nextToken();
 1179                   if (!ok)
 1180                       break;
 1181                   if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
 1182                       ok = false;
 1183                       break;
 1184                   }
 1185                   objNumber[k] = tokens.intValue();
 1186                   ok = tokens.nextToken();
 1187                   if (!ok)
 1188                       break;
 1189                   if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
 1190                       ok = false;
 1191                       break;
 1192                   }
 1193                   address[k] = tokens.intValue() + first;
 1194               }
 1195               if (!ok)
 1196                   throw new IOException("Error reading ObjStm");
 1197               for (int k = 0; k < n; ++k) {
 1198                   if (map.containsKey(k)) {
 1199                       tokens.seek(address[k]);
 1200                       PdfObject obj = readPRObject();
 1201                       xrefObj.set(objNumber[k], obj);
 1202                   }
 1203               }
 1204           }
 1205           finally {
 1206               tokens = saveTokens;
 1207           }
 1208       }
 1209   
 1210       /**
 1211        * Eliminates the reference to the object freeing the memory used by it and clearing
 1212        * the xref entry.
 1213        * @param obj the object. If it's an indirect reference it will be eliminated
 1214        * @return the object or the already erased dereferenced object
 1215        */
 1216       public static PdfObject killIndirect(PdfObject obj) {
 1217           if (obj == null || obj.isNull())
 1218               return null;
 1219           PdfObject ret = getPdfObjectRelease(obj);
 1220           if (obj.isIndirect()) {
 1221               PRIndirectReference ref = (PRIndirectReference)obj;
 1222               PdfReader reader = ref.getReader();
 1223               int n = ref.getNumber();
 1224               reader.xrefObj.set(n, null);
 1225               if (reader.partial)
 1226                   reader.xref[n * 2] = -1;
 1227           }
 1228           return ret;
 1229       }
 1230   
 1231       private void ensureXrefSize(int size) {
 1232           if (size == 0)
 1233               return;
 1234           if (xref == null)
 1235               xref = new int[size];
 1236           else {
 1237               if (xref.length < size) {
 1238                   int xref2[] = new int[size];
 1239                   System.arraycopy(xref, 0, xref2, 0, xref.length);
 1240                   xref = xref2;
 1241               }
 1242           }
 1243       }
 1244   
 1245       protected void readXref() throws IOException {
 1246           hybridXref = false;
 1247           newXrefType = false;
 1248           tokens.seek(tokens.getStartxref());
 1249           tokens.nextToken();
 1250           if (!tokens.getStringValue().equals("startxref"))
 1251               throw new IOException("startxref not found.");
 1252           tokens.nextToken();
 1253           if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
 1254               throw new IOException("startxref is not followed by a number.");
 1255           int startxref = tokens.intValue();
 1256           lastXref = startxref;
 1257           eofPos = tokens.getFilePointer();
 1258           try {
 1259               if (readXRefStream(startxref)) {
 1260                   newXrefType = true;
 1261                   return;
 1262               }
 1263           }
 1264           catch (Exception e) {}
 1265           xref = null;
 1266           tokens.seek(startxref);
 1267           trailer = readXrefSection();
 1268           PdfDictionary trailer2 = trailer;
 1269           while (true) {
 1270               PdfNumber prev = (PdfNumber)trailer2.get(PdfName.PREV);
 1271               if (prev == null)
 1272                   break;
 1273               tokens.seek(prev.intValue());
 1274               trailer2 = readXrefSection();
 1275           }
 1276       }
 1277   
 1278       protected PdfDictionary readXrefSection() throws IOException {
 1279           tokens.nextValidToken();
 1280           if (!tokens.getStringValue().equals("xref"))
 1281               tokens.throwError("xref subsection not found");
 1282           int start = 0;
 1283           int end = 0;
 1284           int pos = 0;
 1285           int gen = 0;
 1286           while (true) {
 1287               tokens.nextValidToken();
 1288               if (tokens.getStringValue().equals("trailer"))
 1289                   break;
 1290               if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
 1291                   tokens.throwError("Object number of the first object in this xref subsection not found");
 1292               start = tokens.intValue();
 1293               tokens.nextValidToken();
 1294               if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
 1295                   tokens.throwError("Number of entries in this xref subsection not found");
 1296               end = tokens.intValue() + start;
 1297               if (start == 1) { // fix incorrect start number
 1298                   int back = tokens.getFilePointer();
 1299                   tokens.nextValidToken();
 1300                   pos = tokens.intValue();
 1301                   tokens.nextValidToken();
 1302                   gen = tokens.intValue();
 1303                   if (pos == 0 && gen == 65535) {
 1304                       --start;
 1305                       --end;
 1306                   }
 1307                   tokens.seek(back);
 1308               }
 1309               ensureXrefSize(end * 2);
 1310               for (int k = start; k < end; ++k) {
 1311                   tokens.nextValidToken();
 1312                   pos = tokens.intValue();
 1313                   tokens.nextValidToken();
 1314                   gen = tokens.intValue();
 1315                   tokens.nextValidToken();
 1316                   int p = k * 2;
 1317                   if (tokens.getStringValue().equals("n")) {
 1318                       if (xref[p] == 0 && xref[p + 1] == 0) {
 1319   //                        if (pos == 0)
 1320   //                            tokens.throwError("File position 0 cross-reference entry in this xref subsection");
 1321                           xref[p] = pos;
 1322                       }
 1323                   }
 1324                   else if (tokens.getStringValue().equals("f")) {
 1325                       if (xref[p] == 0 && xref[p + 1] == 0)
 1326                           xref[p] = -1;
 1327                   }
 1328                   else
 1329                       tokens.throwError("Invalid cross-reference entry in this xref subsection");
 1330               }
 1331           }
 1332           PdfDictionary trailer = (PdfDictionary)readPRObject();
 1333           PdfNumber xrefSize = (PdfNumber)trailer.get(PdfName.SIZE);
 1334           ensureXrefSize(xrefSize.intValue() * 2);
 1335           PdfObject xrs = trailer.get(PdfName.XREFSTM);
 1336           if (xrs != null && xrs.isNumber()) {
 1337               int loc = ((PdfNumber)xrs).intValue();
 1338               try {
 1339                   readXRefStream(loc);
 1340                   newXrefType = true;
 1341                   hybridXref = true;
 1342               }
 1343               catch (IOException e) {
 1344                   xref = null;
 1345                   throw e;
 1346               }
 1347           }
 1348           return trailer;
 1349       }
 1350   
 1351       protected boolean readXRefStream(int ptr) throws IOException {
 1352           tokens.seek(ptr);
 1353           int thisStream = 0;
 1354           if (!tokens.nextToken())
 1355               return false;
 1356           if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
 1357               return false;
 1358           thisStream = tokens.intValue();
 1359           if (!tokens.nextToken() || tokens.getTokenType() != PRTokeniser.TK_NUMBER)
 1360               return false;
 1361           if (!tokens.nextToken() || !tokens.getStringValue().equals("obj"))
 1362               return false;
 1363           PdfObject object = readPRObject();
 1364           PRStream stm = null;
 1365           if (object.isStream()) {
 1366               stm = (PRStream)object;
 1367               if (!PdfName.XREF.equals(stm.get(PdfName.TYPE)))
 1368                   return false;
 1369           }
 1370           else
 1371               return false;
 1372           if (trailer == null) {
 1373               trailer = new PdfDictionary();
 1374               trailer.putAll(stm);
 1375           }
 1376           stm.setLength(((PdfNumber)stm.get(PdfName.LENGTH)).intValue());
 1377           int size = ((PdfNumber)stm.get(PdfName.SIZE)).intValue();
 1378           PdfArray index;
 1379           PdfObject obj = stm.get(PdfName.INDEX);
 1380           if (obj == null) {
 1381               index = new PdfArray();
 1382               index.add(new int[]{0, size});
 1383           }
 1384           else
 1385               index = (PdfArray)obj;
 1386           PdfArray w = (PdfArray)stm.get(PdfName.W);
 1387           int prev = -1;
 1388           obj = stm.get(PdfName.PREV);
 1389           if (obj != null)
 1390               prev = ((PdfNumber)obj).intValue();
 1391           // Each xref pair is a position
 1392           // type 0 -> -1, 0
 1393           // type 1 -> offset, 0
 1394           // type 2 -> index, obj num
 1395           ensureXrefSize(size * 2);
 1396           if (objStmMark == null && !partial)
 1397               objStmMark = new HashMap();
 1398           if (objStmToOffset == null && partial)
 1399               objStmToOffset = new IntHashtable();
 1400           byte b[] = getStreamBytes(stm, tokens.getFile());
 1401           int bptr = 0;
 1402           ArrayList wa = w.getArrayList();
 1403           int wc[] = new int[3];
 1404           for (int k = 0; k < 3; ++k)
 1405               wc[k] = ((PdfNumber)wa.get(k)).intValue();
 1406           ArrayList sections = index.getArrayList();
 1407           for (int idx = 0; idx < sections.size(); idx += 2) {
 1408               int start = ((PdfNumber)sections.get(idx)).intValue();
 1409               int length = ((PdfNumber)sections.get(idx + 1)).intValue();
 1410               ensureXrefSize((start + length) * 2);
 1411               while (length-- > 0) {
 1412                   int type = 1;
 1413                   if (wc[0] > 0) {
 1414                       type = 0;
 1415                       for (int k = 0; k < wc[0]; ++k)
 1416                           type = (type << 8) + (b[bptr++] & 0xff);
 1417                   }
 1418                   int field2 = 0;
 1419                   for (int k = 0; k < wc[1]; ++k)
 1420                       field2 = (field2 << 8) + (b[bptr++] & 0xff);
 1421                   int field3 = 0;
 1422                   for (int k = 0; k < wc[2]; ++k)
 1423                       field3 = (field3 << 8) + (b[bptr++] & 0xff);
 1424                   int base = start * 2;
 1425                   if (xref[base] == 0 && xref[base + 1] == 0) {
 1426                       switch (type) {
 1427                           case 0:
 1428                               xref[base] = -1;
 1429                               break;
 1430                           case 1:
 1431                               xref[base] = field2;
 1432                               break;
 1433                           case 2:
 1434                               xref[base] = field3;
 1435                               xref[base + 1] = field2;
 1436                               if (partial) {
 1437                                   objStmToOffset.put(field2, 0);
 1438                               }
 1439                               else {
 1440                                   Integer on = new Integer(field2);
 1441                                   IntHashtable seq = (IntHashtable)objStmMark.get(on);
 1442                                   if (seq == null) {
 1443                                       seq = new IntHashtable();
 1444                                       seq.put(field3, 1);
 1445                                       objStmMark.put(on, seq);
 1446                                   }
 1447                                   else
 1448                                       seq.put(field3, 1);
 1449                               }
 1450                               break;
 1451                       }
 1452                   }
 1453                   ++start;
 1454               }
 1455           }
 1456           thisStream *= 2;
 1457           if (thisStream < xref.length)
 1458               xref[thisStream] = -1;
 1459   
 1460           if (prev == -1)
 1461               return true;
 1462           return readXRefStream(prev);
 1463       }
 1464   
 1465       protected void rebuildXref() throws IOException {
 1466           hybridXref = false;
 1467           newXrefType = false;
 1468           tokens.seek(0);
 1469           int xr[][] = new int[1024][];
 1470           int top = 0;
 1471           trailer = null;
 1472           byte line[] = new byte[64];
 1473           for (;;) {
 1474               int pos = tokens.getFilePointer();
 1475               if (!tokens.readLineSegment(line))
 1476                   break;
 1477               if (line[0] == 't') {
 1478                   if (!PdfEncodings.convertToString(line, null).startsWith("trailer"))
 1479                       continue;
 1480                   tokens.seek(pos);
 1481                   tokens.nextToken();
 1482                   pos = tokens.getFilePointer();
 1483                   try {
 1484                       PdfDictionary dic = (PdfDictionary)readPRObject();
 1485                       if (dic.get(PdfName.ROOT) != null)
 1486                           trailer = dic;
 1487                       else
 1488                           tokens.seek(pos);
 1489                   }
 1490                   catch (Exception e) {
 1491                       tokens.seek(pos);
 1492                   }
 1493               }
 1494               else if (line[0] >= '0' && line[0] <= '9') {
 1495                   int obj[] = PRTokeniser.checkObjectStart(line);
 1496                   if (obj == null)
 1497                       continue;
 1498                   int num = obj[0];
 1499                   int gen = obj[1];
 1500                   if (num >= xr.length) {
 1501                       int newLength = num * 2;
 1502                       int xr2[][] = new int[newLength][];
 1503                       System.arraycopy(xr, 0, xr2, 0, top);
 1504                       xr = xr2;
 1505                   }
 1506                   if (num >= top)
 1507                       top = num + 1;
 1508                   if (xr[num] == null || gen >= xr[num][1]) {
 1509                       obj[0] = pos;
 1510                       xr[num] = obj;
 1511                   }
 1512               }
 1513           }
 1514           if (trailer == null)
 1515               throw new IOException("trailer not found.");
 1516           xref = new int[top * 2];
 1517           for (int k = 0; k < top; ++k) {
 1518               int obj[] = xr[k];
 1519               if (obj != null)
 1520                   xref[k * 2] = obj[0];
 1521           }
 1522       }
 1523   
 1524       protected PdfDictionary readDictionary() throws IOException {
 1525           PdfDictionary dic = new PdfDictionary();
 1526           while (true) {
 1527               tokens.nextValidToken();
 1528               if (tokens.getTokenType() == PRTokeniser.TK_END_DIC)
 1529                   break;
 1530               if (tokens.getTokenType() != PRTokeniser.TK_NAME)
 1531                   tokens.throwError("Dictionary key is not a name.");
 1532               PdfName name = new PdfName(tokens.getStringValue(), false);
 1533               PdfObject obj = readPRObject();
 1534               int type = obj.type();
 1535               if (-type == PRTokeniser.TK_END_DIC)
 1536                   tokens.throwError("Unexpected '>>'");
 1537               if (-type == PRTokeniser.TK_END_ARRAY)
 1538                   tokens.throwError("Unexpected ']'");
 1539               dic.put(name, obj);
 1540           }
 1541           return dic;
 1542       }
 1543   
 1544       protected PdfArray readArray() throws IOException {
 1545           PdfArray array = new PdfArray();
 1546           while (true) {
 1547               PdfObject obj = readPRObject();
 1548               int type = obj.type();
 1549               if (-type == PRTokeniser.TK_END_ARRAY)
 1550                   break;
 1551               if (-type == PRTokeniser.TK_END_DIC)
 1552                   tokens.throwError("Unexpected '>>'");
 1553               array.add(obj);
 1554           }
 1555           return array;
 1556       }
 1557   
 1558       protected PdfObject readPRObject() throws IOException {
 1559           tokens.nextValidToken();
 1560           int type = tokens.getTokenType();
 1561           switch (type) {
 1562               case PRTokeniser.TK_START_DIC: {
 1563                   PdfDictionary dic = readDictionary();
 1564                   int pos = tokens.getFilePointer();
 1565                   // be careful in the trailer. May not be a "next" token.
 1566                   if (tokens.nextToken() && tokens.getStringValue().equals("stream")) {
 1567                       int ch = tokens.read();
 1568                       if (ch != '\n')
 1569                           ch = tokens.read();
 1570                       if (ch != '\n')
 1571                           tokens.backOnePosition(ch);
 1572                       PRStream stream = new PRStream(this, tokens.getFilePointer());
 1573                       stream.putAll(dic);
 1574                       stream.setObjNum(objNum, objGen);
 1575                       return stream;
 1576                   }
 1577                   else {
 1578                       tokens.seek(pos);
 1579                       return dic;
 1580                   }
 1581               }
 1582               case PRTokeniser.TK_START_ARRAY:
 1583                   return readArray();
 1584               case PRTokeniser.TK_NUMBER:
 1585                   return new PdfNumber(tokens.getStringValue());
 1586               case PRTokeniser.TK_STRING:
 1587                   PdfString str = new PdfString(tokens.getStringValue(), null).setHexWriting(tokens.isHexString());
 1588                   str.setObjNum(objNum, objGen);
 1589                   if (strings != null)
 1590                       strings.add(str);
 1591                   return str;
 1592               case PRTokeniser.TK_NAME:
 1593                   return new PdfName(tokens.getStringValue(), false);
 1594               case PRTokeniser.TK_REF:
 1595                   int num = tokens.getReference();
 1596                   PRIndirectReference ref = new PRIndirectReference(this, num, tokens.getGeneration());
 1597                   return ref;
 1598               default:
 1599                   String sv = tokens.getStringValue();
 1600                   if ("null".equals(sv))
 1601                       return PdfNull.PDFNULL;
 1602                   else if ("true".equals(sv))
 1603                       return PdfBoolean.PDFTRUE;
 1604                   else if ("false".equals(sv))
 1605                       return PdfBoolean.PDFFALSE;
 1606                   return new PdfLiteral(-type, tokens.getStringValue());
 1607           }
 1608       }
 1609   
 1610       /** Decodes a stream that has the FlateDecode filter.
 1611        * @param in the input data
 1612        * @return the decoded data
 1613        */
 1614       public static byte[] FlateDecode(byte in[]) {
 1615           byte b[] = FlateDecode(in, true);
 1616           if (b == null)
 1617               return FlateDecode(in, false);
 1618           return b;
 1619       }
 1620   
 1621       /**
 1622        * @param in
 1623        * @param dicPar
 1624        * @return a byte array
 1625        */
 1626       public static byte[] decodePredictor(byte in[], PdfObject dicPar) {
 1627           if (dicPar == null || !dicPar.isDictionary())
 1628               return in;
 1629           PdfDictionary dic = (PdfDictionary)dicPar;
 1630           PdfObject obj = getPdfObject(dic.get(PdfName.PREDICTOR));
 1631           if (obj == null || !obj.isNumber())
 1632               return in;
 1633           int predictor = ((PdfNumber)obj).intValue();
 1634           if (predictor < 10)
 1635               return in;
 1636           int width = 1;
 1637           obj = getPdfObject(dic.get(PdfName.COLUMNS));
 1638           if (obj != null && obj.isNumber())
 1639               width = ((PdfNumber)obj).intValue();
 1640           int colors = 1;
 1641           obj = getPdfObject(dic.get(PdfName.COLORS));
 1642           if (obj != null && obj.isNumber())
 1643               colors = ((PdfNumber)obj).intValue();
 1644           int bpc = 8;
 1645           obj = getPdfObject(dic.get(PdfName.BITSPERCOMPONENT));
 1646           if (obj != null && obj.isNumber())
 1647               bpc = ((PdfNumber)obj).intValue();
 1648           DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(in));
 1649           ByteArrayOutputStream fout = new ByteArrayOutputStream(in.length);
 1650           int bytesPerPixel = colors * bpc / 8;
 1651           int bytesPerRow = (colors*width*bpc + 7)/8;
 1652           byte[] curr = new byte[bytesPerRow];
 1653           byte[] prior = new byte[bytesPerRow];
 1654   
 1655           // Decode the (sub)image row-by-row
 1656           while (true) {
 1657               // Read the filter type byte and a row of data
 1658               int filter = 0;
 1659               try {
 1660                   filter = dataStream.read();
 1661                   if (filter < 0) {
 1662                       return fout.toByteArray();
 1663                   }
 1664                   dataStream.readFully(curr, 0, bytesPerRow);
 1665               } catch (Exception e) {
 1666                   return fout.toByteArray();
 1667               }
 1668   
 1669               switch (filter) {
 1670                   case 0: //PNG_FILTER_NONE
 1671                       break;
 1672                   case 1: //PNG_FILTER_SUB
 1673                       for (int i = bytesPerPixel; i < bytesPerRow; i++) {
 1674                           curr[i] += curr[i - bytesPerPixel];
 1675                       }
 1676                       break;
 1677                   case 2: //PNG_FILTER_UP
 1678                       for (int i = 0; i < bytesPerRow; i++) {
 1679                           curr[i] += prior[i];
 1680                       }
 1681                       break;
 1682                   case 3: //PNG_FILTER_AVERAGE
 1683                       for (int i = 0; i < bytesPerPixel; i++) {
 1684                           curr[i] += prior[i] / 2;
 1685                       }
 1686                       for (int i = bytesPerPixel; i < bytesPerRow; i++) {
 1687                           curr[i] += ((curr[i - bytesPerPixel] & 0xff) + (prior[i] & 0xff))/2;
 1688                       }
 1689                       break;
 1690                   case 4: //PNG_FILTER_PAETH
 1691                       for (int i = 0; i < bytesPerPixel; i++) {
 1692                           curr[i] += prior[i];
 1693                       }
 1694   
 1695                       for (int i = bytesPerPixel; i < bytesPerRow; i++) {
 1696                           int a = curr[i - bytesPerPixel] & 0xff;
 1697                           int b = prior[i] & 0xff;
 1698                           int c = prior[i - bytesPerPixel] & 0xff;
 1699   
 1700                           int p = a + b - c;
 1701                           int pa = Math.abs(p - a);
 1702                           int pb = Math.abs(p - b);
 1703                           int pc = Math.abs(p - c);
 1704   
 1705                           int ret;
 1706   
 1707                           if ((pa <= pb) && (pa <= pc)) {
 1708                               ret = a;
 1709                           } else if (pb <= pc) {
 1710                               ret = b;
 1711                           } else {
 1712                               ret = c;
 1713                           }
 1714                           curr[i] += (byte)(ret);
 1715                       }
 1716                       break;
 1717                   default:
 1718                       // Error -- unknown filter type
 1719                       throw new RuntimeException("PNG filter unknown.");
 1720               }
 1721               try {
 1722                   fout.write(curr);
 1723               }
 1724               catch (IOException ioe) {
 1725                   // Never happens
 1726               }
 1727   
 1728               // Swap curr and prior
 1729               byte[] tmp = prior;
 1730               prior = curr;
 1731               curr = tmp;
 1732           }
 1733       }
 1734   
 1735       /** A helper to FlateDecode.
 1736        * @param in the input data
 1737        * @param strict <CODE>true</CODE> to read a correct stream. <CODE>false</CODE>
 1738        * to try to read a corrupted stream
 1739        * @return the decoded data
 1740        */
 1741       public static byte[] FlateDecode(byte in[], boolean strict) {
 1742           ByteArrayInputStream stream = new ByteArrayInputStream(in);
 1743           InflaterInputStream zip = new InflaterInputStream(stream);
 1744           ByteArrayOutputStream out = new ByteArrayOutputStream();
 1745           byte b[] = new byte[strict ? 4092 : 1];
 1746           try {
 1747               int n;
 1748               while ((n = zip.read(b)) >= 0) {
 1749                   out.write(b, 0, n);
 1750               }
 1751               zip.close();
 1752               out.close();
 1753               return out.toByteArray();
 1754           }
 1755           catch (Exception e) {
 1756               if (strict)
 1757                   return null;
 1758               return out.toByteArray();
 1759           }
 1760       }
 1761   
 1762       /** Decodes a stream that has the ASCIIHexDecode filter.
 1763        * @param in the input data
 1764        * @return the decoded data
 1765        */
 1766       public static byte[] ASCIIHexDecode(byte in[]) {
 1767           ByteArrayOutputStream out = new ByteArrayOutputStream();
 1768           boolean first = true;
 1769           int n1 = 0;
 1770           for (int k = 0; k < in.length; ++k) {
 1771               int ch = in[k] & 0xff;
 1772               if (ch == '>')
 1773                   break;
 1774               if (PRTokeniser.isWhitespace(ch))
 1775                   continue;
 1776               int n = PRTokeniser.getHex(ch);
 1777               if (n == -1)
 1778                   throw new RuntimeException("Illegal character in ASCIIHexDecode.");
 1779               if (first)
 1780                   n1 = n;
 1781               else
 1782                   out.write((byte)((n1 << 4) + n));
 1783               first = !first;
 1784           }
 1785           if (!first)
 1786               out.write((byte)(n1 << 4));
 1787           return out.toByteArray();
 1788       }
 1789   
 1790       /** Decodes a stream that has the ASCII85Decode filter.
 1791        * @param in the input data
 1792        * @return the decoded data
 1793        */
 1794       public static byte[] ASCII85Decode(byte in[]) {
 1795           ByteArrayOutputStream out = new ByteArrayOutputStream();
 1796           int state = 0;
 1797           int chn[] = new int[5];
 1798           for (int k = 0; k < in.length; ++k) {
 1799               int ch = in[k] & 0xff;
 1800               if (ch == '~')
 1801                   break;
 1802               if (PRTokeniser.isWhitespace(ch))
 1803                   continue;
 1804               if (ch == 'z' && state == 0) {
 1805                   out.write(0);
 1806                   out.write(0);
 1807                   out.write(0);
 1808                   out.write(0);
 1809                   continue;
 1810               }
 1811               if (ch < '!' || ch > 'u')
 1812                   throw new RuntimeException("Illegal character in ASCII85Decode.");
 1813               chn[state] = ch - '!';
 1814               ++state;
 1815               if (state == 5) {
 1816                   state = 0;
 1817                   int r = 0;
 1818                   for (int j = 0; j < 5; ++j)
 1819                       r = r * 85 + chn[j];
 1820                   out.write((byte)(r >> 24));
 1821                   out.write((byte)(r >> 16));
 1822                   out.write((byte)(r >> 8));
 1823                   out.write((byte)r);
 1824               }
 1825           }
 1826           int r = 0;
 1827           // We'll ignore the next two lines for the sake of perpetuating broken PDFs
 1828   //        if (state == 1)
 1829   //            throw new RuntimeException("Illegal length in ASCII85Decode.");
 1830           if (state == 2) {
 1831               r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + 85 * 85 * 85  + 85 * 85 + 85;
 1832               out.write((byte)(r >> 24));
 1833           }
 1834           else if (state == 3) {
 1835               r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85  + chn[2] * 85 * 85 + 85 * 85 + 85;
 1836               out.write((byte)(r >> 24));
 1837               out.write((byte)(r >> 16));
 1838           }
 1839           else if (state == 4) {
 1840               r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85  + chn[2] * 85 * 85  + chn[3] * 85 + 85;
 1841               out.write((byte)(r >> 24));
 1842               out.write((byte)(r >> 16));
 1843               out.write((byte)(r >> 8));
 1844           }
 1845           return out.toByteArray();
 1846       }
 1847   
 1848       /** Decodes a stream that has the LZWDecode filter.
 1849        * @param in the input data
 1850        * @return the decoded data
 1851        */
 1852       public static byte[] LZWDecode(byte in[]) {
 1853           ByteArrayOutputStream out = new ByteArrayOutputStream();
 1854           LZWDecoder lzw = new LZWDecoder();
 1855           lzw.decode(in, out);
 1856           return out.toByteArray();
 1857       }
 1858   
 1859       /** Checks if the document had errors and was rebuilt.
 1860        * @return true if rebuilt.
 1861        *
 1862        */
 1863       public boolean isRebuilt() {
 1864           return this.rebuilt;
 1865       }
 1866   
 1867       /** Gets the dictionary that represents a page.
 1868        * @param pageNum the page number. 1 is the first
 1869        * @return the page dictionary
 1870        */
 1871       public PdfDictionary getPageN(int pageNum) {
 1872           PdfDictionary dic = pageRefs.getPageN(pageNum);
 1873           if (dic == null)
 1874               return null;
 1875           if (appendable)
 1876               dic.setIndRef(pageRefs.getPageOrigRef(pageNum));
 1877           return dic;
 1878       }
 1879   
 1880       /**
 1881        * @param pageNum
 1882        * @return a Dictionary object
 1883        */
 1884       public PdfDictionary getPageNRelease(int pageNum) {
 1885           PdfDictionary dic = getPageN(pageNum);
 1886           pageRefs.releasePage(pageNum);
 1887           return dic;
 1888       }
 1889   
 1890       /**
 1891        * @param pageNum
 1892        */
 1893       public void releasePage(int pageNum) {
 1894           pageRefs.releasePage(pageNum);
 1895       }
 1896   
 1897       /**
 1898        *
 1899        */
 1900       public void resetReleasePage() {
 1901           pageRefs.resetReleasePage();
 1902       }
 1903   
 1904       /** Gets the page reference to this page.
 1905        * @param pageNum the page number. 1 is the first
 1906        * @return the page reference
 1907        */
 1908       public PRIndirectReference getPageOrigRef(int pageNum) {
 1909           return pageRefs.getPageOrigRef(pageNum);
 1910       }
 1911   
 1912       /** Gets the contents of the page.
 1913        * @param pageNum the page number. 1 is the first
 1914        * @param file the location of the PDF document
 1915        * @throws IOException on error
 1916        * @return the content
 1917        */
 1918       public byte[] getPageContent(int pageNum, RandomAccessFileOrArray file) throws IOException{
 1919           PdfDictionary page = getPageNRelease(pageNum);
 1920           if (page == null)
 1921               return null;
 1922           PdfObject contents = getPdfObjectRelease(page.get(PdfName.CONTENTS));
 1923           if (contents == null)
 1924               return new byte[0];
 1925           ByteArrayOutputStream bout = null;
 1926           if (contents.isStream()) {
 1927               return getStreamBytes((PRStream)contents, file);
 1928           }
 1929           else if (contents.isArray()) {
 1930               PdfArray array = (PdfArray)contents;
 1931               ArrayList list = array.getArrayList();
 1932               bout = new ByteArrayOutputStream();
 1933               for (int k = 0; k < list.size(); ++k) {
 1934                   PdfObject item = getPdfObjectRelease((PdfObject)list.get(k));
 1935                   if (item == null || !item.isStream())
 1936                       continue;
 1937                   byte[] b = getStreamBytes((PRStream)item, file);
 1938                   bout.write(b);
 1939                   if (k != list.size() - 1)
 1940                       bout.write('\n');
 1941               }
 1942               return bout.toByteArray();
 1943           }
 1944           else
 1945               return new byte[0];
 1946       }
 1947   
 1948       /** Gets the contents of the page.
 1949        * @param pageNum the page number. 1 is the first
 1950        * @throws IOException on error
 1951        * @return the content
 1952        */
 1953       public byte[] getPageContent(int pageNum) throws IOException{
 1954           RandomAccessFileOrArray rf = getSafeFile();
 1955           try {
 1956               rf.reOpen();
 1957               return getPageContent(pageNum, rf);
 1958           }
 1959           finally {
 1960               try{rf.close();}catch(Exception e){}
 1961           }
 1962       }
 1963   
 1964       protected void killXref(PdfObject obj) {
 1965           if (obj == null)
 1966               return;
 1967           if ((obj instanceof PdfIndirectReference) && !obj.isIndirect())
 1968               return;
 1969           switch (obj.type()) {
 1970               case PdfObject.INDIRECT: {
 1971                   int xr = ((PRIndirectReference)obj).getNumber();
 1972                   obj = (PdfObject)xrefObj.get(xr);
 1973                   xrefObj.set(xr, null);
 1974                   freeXref = xr;
 1975                   killXref(obj);
 1976                   break;
 1977               }
 1978               case PdfObject.ARRAY: {
 1979                   ArrayList t = ((PdfArray)obj).getArrayList();
 1980                   for (int i = 0; i < t.size(); ++i)
 1981                       killXref((PdfObject)t.get(i));
 1982                   break;
 1983               }
 1984               case PdfObject.STREAM:
 1985               case PdfObject.DICTIONARY: {
 1986                   PdfDictionary dic = (PdfDictionary)obj;
 1987                   for (Iterator i = dic.getKeys().iterator(); i.hasNext();){
 1988                       killXref(dic.get((PdfName)i.next()));
 1989                   }
 1990                   break;
 1991               }
 1992           }
 1993       }
 1994   
 1995       /** Sets the contents of the page.
 1996        * @param content the new page content
 1997        * @param pageNum the page number. 1 is the first
 1998        */
 1999       public void setPageContent(int pageNum, byte content[]) {
 2000       	setPageContent(pageNum, content, PdfStream.DEFAULT_COMPRESSION);
 2001       }
 2002       /** Sets the contents of the page.
 2003        * @param content the new page content
 2004        * @param pageNum the page number. 1 is the first
 2005        * @since	2.1.3	(the method already existed without param compressionLevel)
 2006        */
 2007       public void setPageContent(int pageNum, byte content[], int compressionLevel) {
 2008           PdfDictionary page = getPageN(pageNum);
 2009           if (page == null)
 2010               return;
 2011           PdfObject contents = page.get(PdfName.CONTENTS);
 2012           freeXref = -1;
 2013           killXref(contents);
 2014           if (freeXref == -1) {
 2015               xrefObj.add(null);
 2016               freeXref = xrefObj.size() - 1;
 2017           }
 2018           page.put(PdfName.CONTENTS, new PRIndirectReference(this, freeXref));
 2019           xrefObj.set(freeXref, new PRStream(this, content, compressionLevel));
 2020       }
 2021   
 2022       /** Get the content from a stream applying the required filters.
 2023        * @param stream the stream
 2024        * @param file the location where the stream is
 2025        * @throws IOException on error
 2026        * @return the stream content
 2027        */
 2028       public static byte[] getStreamBytes(PRStream stream, RandomAccessFileOrArray file) throws IOException {
 2029           PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER));
 2030           byte[] b = getStreamBytesRaw(stream, file);
 2031           ArrayList filters = new ArrayList();
 2032           if (filter != null) {
 2033               if (filter.isName())
 2034                   filters.add(filter);
 2035               else if (filter.isArray())
 2036                   filters = ((PdfArray)filter).getArrayList();
 2037           }
 2038           ArrayList dp = new ArrayList();
 2039           PdfObject dpo = getPdfObjectRelease(stream.get(PdfName.DECODEPARMS));
 2040           if (dpo == null || (!dpo.isDictionary() && !dpo.isArray()))
 2041               dpo = getPdfObjectRelease(stream.get(PdfName.DP));
 2042           if (dpo != null) {
 2043               if (dpo.isDictionary())
 2044                   dp.add(dpo);
 2045               else if (dpo.isArray())
 2046                   dp = ((PdfArray)dpo).getArrayList();
 2047           }
 2048           String name;
 2049           for (int j = 0; j < filters.size(); ++j) {
 2050               name = ((PdfName)getPdfObjectRelease((PdfObject)filters.get(j))).toString();
 2051               if (name.equals("/FlateDecode") || name.equals("/Fl")) {
 2052                   b = FlateDecode(b);
 2053                   PdfObject dicParam = null;
 2054                   if (j < dp.size()) {
 2055                       dicParam = (PdfObject)dp.get(j);
 2056                       b = decodePredictor(b, dicParam);
 2057                   }
 2058               }
 2059               else if (name.equals("/ASCIIHexDecode") || name.equals("/AHx"))
 2060                   b = ASCIIHexDecode(b);
 2061               else if (name.equals("/ASCII85Decode") || name.equals("/A85"))
 2062                   b = ASCII85Decode(b);
 2063               else if (name.equals("/LZWDecode")) {
 2064                   b = LZWDecode(b);
 2065                   PdfObject dicParam = null;
 2066                   if (j < dp.size()) {
 2067                       dicParam = (PdfObject)dp.get(j);
 2068                       b = decodePredictor(b, dicParam);
 2069                   }
 2070               }
 2071               else if (name.equals("/Crypt")) {
 2072               }
 2073               else
 2074                   throw new IOException("The filter " + name + " is not supported.");
 2075           }
 2076           return b;
 2077       }
 2078   
 2079       /** Get the content from a stream applying the required filters.
 2080        * @param stream the stream
 2081        * @throws IOException on error
 2082        * @return the stream content
 2083        */
 2084       public static byte[] getStreamBytes(PRStream stream) throws IOException {
 2085           RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
 2086           try {
 2087               rf.reOpen();
 2088               return getStreamBytes(stream, rf);
 2089           }
 2090           finally {
 2091               try{rf.close();}catch(Exception e){}
 2092           }
 2093       }
 2094   
 2095       /** Get the content from a stream as it is without applying any filter.
 2096        * @param stream the stream
 2097        * @param file the location where the stream is
 2098        * @throws IOException on error
 2099        * @return the stream content
 2100        */
 2101       public static byte[] getStreamBytesRaw(PRStream stream, RandomAccessFileOrArray file) throws IOException {
 2102           PdfReader reader = stream.getReader();
 2103           byte b[];
 2104           if (stream.getOffset() < 0)
 2105               b = stream.getBytes();
 2106           else {
 2107               b = new byte[stream.getLength()];
 2108               file.seek(stream.getOffset());
 2109               file.readFully(b);
 2110               PdfEncryption decrypt = reader.getDecrypt();
 2111               if (decrypt != null) {
 2112                   PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER));
 2113                   ArrayList filters = new ArrayList();
 2114                   if (filter != null) {
 2115                       if (filter.isName())
 2116                           filters.add(filter);
 2117                       else if (filter.isArray())
 2118                           filters = ((PdfArray)filter).getArrayList();
 2119                   }
 2120                   boolean skip = false;
 2121                   for (int k = 0; k < filters.size(); ++k) {
 2122                       PdfObject obj = getPdfObjectRelease((PdfObject)filters.get(k));
 2123                       if (obj != null && obj.toString().equals("/Crypt")) {
 2124                           skip = true;
 2125                           break;
 2126                       }
 2127                   }
 2128                   if (!skip) {
 2129                       decrypt.setHashKey(stream.getObjNum(), stream.getObjGen());
 2130                       b = decrypt.decryptByteArray(b);
 2131                   }
 2132               }
 2133           }
 2134           return b;
 2135       }
 2136   
 2137       /** Get the content from a stream as it is without applying any filter.
 2138        * @param stream the stream
 2139        * @throws IOException on error
 2140        * @return the stream content
 2141        */
 2142       public static byte[] getStreamBytesRaw(PRStream stream) throws IOException {
 2143           RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
 2144           try {
 2145               rf.reOpen();
 2146               return getStreamBytesRaw(stream, rf);
 2147           }
 2148           finally {
 2149               try{rf.close();}catch(Exception e){}
 2150           }
 2151       }
 2152   
 2153       /** Eliminates shared streams if they exist. */
 2154       public void eliminateSharedStreams() {
 2155           if (!sharedStreams)
 2156               return;
 2157           sharedStreams = false;
 2158           if (pageRefs.size() == 1)
 2159               return;
 2160           ArrayList newRefs = new ArrayList();
 2161           ArrayList newStreams = new ArrayList();
 2162           IntHashtable visited = new IntHashtable();
 2163           for (int k = 1; k <= pageRefs.size(); ++k) {
 2164               PdfDictionary page = pageRefs.getPageN(k);
 2165               if (page == null)
 2166                   continue;
 2167               PdfObject contents = getPdfObject(page.get(PdfName.CONTENTS));
 2168               if (contents == null)
 2169                   continue;
 2170               if (contents.isStream()) {
 2171                   PRIndirectReference ref = (PRIndirectReference)page.get(PdfName.CONTENTS);
 2172                   if (visited.containsKey(ref.getNumber())) {
 2173                       // need to duplicate
 2174                       newRefs.add(ref);
 2175                       newStreams.add(new PRStream((PRStream)contents, null));
 2176                   }
 2177                   else
 2178                       visited.put(ref.getNumber(), 1);
 2179               }
 2180               else if (contents.isArray()) {
 2181                   PdfArray array = (PdfArray)contents;
 2182                   ArrayList list = array.getArrayList();
 2183                   for (int j = 0; j < list.size(); ++j) {
 2184                       PRIndirectReference ref = (PRIndirectReference)list.get(j);
 2185                       if (visited.containsKey(ref.getNumber())) {
 2186                           // need to duplicate
 2187                           newRefs.add(ref);
 2188                           newStreams.add(new PRStream((PRStream)getPdfObject(ref), null));
 2189                       }
 2190                       else
 2191                           visited.put(ref.getNumber(), 1);
 2192                   }
 2193               }
 2194           }
 2195           if (newStreams.isEmpty())
 2196               return;
 2197           for (int k = 0; k < newStreams.size(); ++k) {
 2198               xrefObj.add(newStreams.get(k));
 2199               PRIndirectReference ref = (PRIndirectReference)newRefs.get(k);
 2200               ref.setNumber(xrefObj.size() - 1, 0);
 2201           }
 2202       }
 2203   
 2204       /** Checks if the document was changed.
 2205        * @return <CODE>true</CODE> if the document was changed,
 2206        * <CODE>false</CODE> otherwise
 2207        */
 2208       public boolean isTampered() {
 2209           return tampered;
 2210       }
 2211   
 2212       /**
 2213        * Sets the tampered state. A tampered PdfReader cannot be reused in PdfStamper.
 2214        * @param tampered the tampered state
 2215        */
 2216       public void setTampered(boolean tampered) {
 2217           this.tampered = tampered;
 2218           pageRefs.keepPages();
 2219       }
 2220   
 2221       /** Gets the XML metadata.
 2222        * @throws IOException on error
 2223        * @return the XML metadata
 2224        */
 2225       public byte[] getMetadata() throws IOException {
 2226           PdfObject obj = getPdfObject(catalog.get(PdfName.METADATA));
 2227           if (!(obj instanceof PRStream))
 2228               return null;
 2229           RandomAccessFileOrArray rf = getSafeFile();
 2230           byte b[] = null;
 2231           try {
 2232               rf.reOpen();
 2233               b = getStreamBytes((PRStream)obj, rf);
 2234           }
 2235           finally {
 2236               try {
 2237                   rf.close();
 2238               }
 2239               catch (Exception e) {
 2240                   // empty on purpose
 2241               }
 2242           }
 2243           return b;
 2244       }
 2245   
 2246       /**
 2247        * Gets the byte address of the last xref table.
 2248        * @return the byte address of the last xref table
 2249        */
 2250       public int getLastXref() {
 2251           return lastXref;
 2252       }
 2253   
 2254       /**
 2255        * Gets the number of xref objects.
 2256        * @return the number of xref objects
 2257        */
 2258       public int getXrefSize() {
 2259           return xrefObj.size();
 2260       }
 2261   
 2262       /**
 2263        * Gets the byte address of the %%EOF marker.
 2264        * @return the byte address of the %%EOF marker
 2265        */
 2266       public int getEofPos() {
 2267           return eofPos;
 2268       }
 2269   
 2270       /**
 2271        * Gets the PDF version. Only the last version char is returned. For example
 2272        * version 1.4 is returned as '4'.
 2273        * @return the PDF version
 2274        */
 2275       public char getPdfVersion() {
 2276           return pdfVersion;
 2277       }
 2278   
 2279       /**
 2280        * Returns <CODE>true</CODE> if the PDF is encrypted.
 2281        * @return <CODE>true</CODE> if the PDF is encrypted
 2282        */
 2283       public boolean isEncrypted() {
 2284           return encrypted;
 2285       }
 2286   
 2287       /**
 2288        * Gets the encryption permissions. It can be used directly in
 2289        * <CODE>PdfWriter.setEncryption()</CODE>.
 2290        * @return the encryption permissions
 2291        */
 2292       public int getPermissions() {
 2293           return pValue;
 2294       }
 2295   
 2296       /**
 2297        * Returns <CODE>true</CODE> if the PDF has a 128 bit key encryption.
 2298        * @return <CODE>true</CODE> if the PDF has a 128 bit key encryption
 2299        */
 2300       public boolean is128Key() {
 2301           return rValue == 3;
 2302       }
 2303   
 2304       /**
 2305        * Gets the trailer dictionary
 2306        * @return the trailer dictionary
 2307        */
 2308       public PdfDictionary getTrailer() {
 2309           return trailer;
 2310       }
 2311   
 2312       PdfEncryption getDecrypt() {
 2313           return decrypt;
 2314       }
 2315   
 2316       static boolean equalsn(byte a1[], byte a2[]) {
 2317           int length = a2.length;
 2318           for (int k = 0; k < length; ++k) {
 2319               if (a1[k] != a2[k])
 2320                   return false;
 2321           }
 2322           return true;
 2323       }
 2324   
 2325       static boolean existsName(PdfDictionary dic, PdfName key, PdfName value) {
 2326           PdfObject type = getPdfObjectRelease(dic.get(key));
 2327           if (type == null || !type.isName())
 2328               return false;
 2329           PdfName name = (PdfName)type;
 2330           return name.equals(value);
 2331       }
 2332   
 2333       static String getFontName(PdfDictionary dic) {
 2334           if (dic == null)
 2335               return null;
 2336           PdfObject type = getPdfObjectRelease(dic.get(PdfName.BASEFONT));
 2337           if (type == null || !type.isName())
 2338               return null;
 2339           return PdfName.decodeName(type.toString());
 2340       }
 2341   
 2342       static String getSubsetPrefix(PdfDictionary dic) {
 2343           if (dic == null)
 2344               return null;
 2345           String s = getFontName(dic);
 2346           if (s == null)
 2347               return null;
 2348           if (s.length() < 8 || s.charAt(6) != '+')
 2349               return null;
 2350           for (int k = 0; k < 6; ++k) {
 2351               char c = s.charAt(k);
 2352               if (c < 'A' || c > 'Z')
 2353                   return null;
 2354           }
 2355           return s;
 2356       }
 2357   
 2358       /** Finds all the font subsets and changes the prefixes to some
 2359        * random values.
 2360        * @return the number of font subsets altered
 2361        */
 2362       public int shuffleSubsetNames() {
 2363           int total = 0;
 2364           for (int k = 1; k < xrefObj.size(); ++k) {
 2365               PdfObject obj = getPdfObjectRelease(k);
 2366               if (obj == null || !obj.isDictionary())
 2367                   continue;
 2368               PdfDictionary dic = (PdfDictionary)obj;
 2369               if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
 2370                   continue;
 2371               if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
 2372                   || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
 2373                   || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) {
 2374                   String s = getSubsetPrefix(dic);
 2375                   if (s == null)
 2376                       continue;
 2377                   String ns = BaseFont.createSubsetPrefix() + s.substring(7);
 2378                   PdfName newName = new PdfName(ns);
 2379                   dic.put(PdfName.BASEFONT, newName);
 2380                   setXrefPartialObject(k, dic);
 2381                   ++total;
 2382                   PdfDictionary fd = (PdfDictionary)getPdfObject(dic.get(PdfName.FONTDESCRIPTOR));
 2383                   if (fd == null)
 2384                       continue;
 2385                   fd.put(PdfName.FONTNAME, newName);
 2386               }
 2387               else if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE0)) {
 2388                   String s = getSubsetPrefix(dic);
 2389                   PdfArray arr = (PdfArray)getPdfObject(dic.get(PdfName.DESCENDANTFONTS));
 2390                   if (arr == null)
 2391                       continue;
 2392                   ArrayList list = arr.getArrayList();
 2393                   if (list.isEmpty())
 2394                       continue;
 2395                   PdfDictionary desc = (PdfDictionary)getPdfObject((PdfObject)list.get(0));
 2396                   String sde = getSubsetPrefix(desc);
 2397                   if (sde == null)
 2398                       continue;
 2399                   String ns = BaseFont.createSubsetPrefix();
 2400                   if (s != null)
 2401                       dic.put(PdfName.BASEFONT, new PdfName(ns + s.substring(7)));
 2402                   setXrefPartialObject(k, dic);
 2403                   PdfName newName = new PdfName(ns + sde.substring(7));
 2404                   desc.put(PdfName.BASEFONT, newName);
 2405                   ++total;
 2406                   PdfDictionary fd = (PdfDictionary)getPdfObject(desc.get(PdfName.FONTDESCRIPTOR));
 2407                   if (fd == null)
 2408                       continue;
 2409                   fd.put(PdfName.FONTNAME, newName);
 2410               }
 2411           }
 2412           return total;
 2413       }
 2414   
 2415       /** Finds all the fonts not subset but embedded and marks them as subset.
 2416        * @return the number of fonts altered
 2417        */
 2418       public int createFakeFontSubsets() {
 2419           int total = 0;
 2420           for (int k = 1; k < xrefObj.size(); ++k) {
 2421               PdfObject obj = getPdfObjectRelease(k);
 2422               if (obj == null || !obj.isDictionary())
 2423                   continue;
 2424               PdfDictionary dic = (PdfDictionary)obj;
 2425               if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
 2426                   continue;
 2427               if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
 2428                   || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
 2429                   || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) {
 2430                   String s = getSubsetPrefix(dic);
 2431                   if (s != null)
 2432                       continue;
 2433                   s = getFontName(dic);
 2434                   if (s == null)
 2435                       continue;
 2436                   String ns = BaseFont.createSubsetPrefix() + s;
 2437                   PdfDictionary fd = (PdfDictionary)getPdfObjectRelease(dic.get(PdfName.FONTDESCRIPTOR));
 2438                   if (fd == null)
 2439                       continue;
 2440                   if (fd.get(PdfName.FONTFILE) == null && fd.get(PdfName.FONTFILE2) == null
 2441                       && fd.get(PdfName.FONTFILE3) == null)
 2442                       continue;
 2443                   fd = (PdfDictionary)getPdfObject(dic.get(PdfName.FONTDESCRIPTOR));
 2444                   PdfName newName = new PdfName(ns);
 2445                   dic.put(PdfName.BASEFONT, newName);
 2446                   fd.put(PdfName.FONTNAME, newName);
 2447                   setXrefPartialObject(k, dic);
 2448                   ++total;
 2449               }
 2450           }
 2451           return total;
 2452       }
 2453   
 2454       private static PdfArray getNameArray(PdfObject obj) {
 2455           if (obj == null)
 2456               return null;
 2457           obj = getPdfObjectRelease(obj);
 2458           if (obj == null)
 2459               return null;
 2460           if (obj.isArray())
 2461               return (PdfArray)obj;
 2462           else if (obj.isDictionary()) {
 2463               PdfObject arr2 = getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.D));
 2464               if (arr2 != null && arr2.isArray())
 2465                   return (PdfArray)arr2;
 2466           }
 2467           return null;
 2468       }
 2469   
 2470       /**
 2471        * Gets all the named destinations as an <CODE>HashMap</CODE>. The key is the name
 2472        * and the value is the destinations array.
 2473        * @return gets all the named destinations
 2474        */
 2475       public HashMap getNamedDestination() {
 2476           HashMap names = getNamedDestinationFromNames();
 2477           names.putAll(getNamedDestinationFromStrings());
 2478           return names;
 2479       }
 2480   
 2481       /**
 2482        * Gets the named destinations from the /Dests key in the catalog as an <CODE>HashMap</CODE>. The key is the name
 2483        * and the value is the destinations array.
 2484        * @return gets the named destinations
 2485        */
 2486       public HashMap getNamedDestinationFromNames() {
 2487           HashMap names = new HashMap();
 2488           if (catalog.get(PdfName.DESTS) != null) {
 2489               PdfDictionary dic = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.DESTS));
 2490               if (dic == null)
 2491                   return names;
 2492               Set keys = dic.getKeys();
 2493               for (Iterator it = keys.iterator(); it.hasNext();) {
 2494                   PdfName key = (PdfName)it.next();
 2495                   String name = PdfName.decodeName(key.toString());
 2496                   PdfArray arr = getNameArray(dic.get(key));
 2497                   if (arr != null)
 2498                       names.put(name, arr);
 2499               }
 2500           }
 2501           return names;
 2502       }
 2503   
 2504       /**
 2505        * Gets the named destinations from the /Names key in the catalog as an <CODE>HashMap</CODE>. The key is the name
 2506        * and the value is the destinations array.
 2507        * @return gets the named destinations
 2508        */
 2509       public HashMap getNamedDestinationFromStrings() {
 2510           if (catalog.get(PdfName.NAMES) != null) {
 2511               PdfDictionary dic = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.NAMES));
 2512               if (dic != null) {
 2513                   dic = (PdfDictionary)getPdfObjectRelease(dic.get(PdfName.DESTS));
 2514                   if (dic != null) {
 2515                       HashMap names = PdfNameTree.readTree(dic);
 2516                       for (Iterator it = names.entrySet().iterator(); it.hasNext();) {
 2517                           Map.Entry entry = (Map.Entry)it.next();
 2518                           PdfArray arr = getNameArray((PdfObject)entry.getValue());
 2519                           if (arr != null)
 2520                               entry.setValue(arr);
 2521                           else
 2522                               it.remove();
 2523                       }
 2524                       return names;
 2525                   }
 2526               }
 2527           }
 2528           return new HashMap();
 2529       }
 2530   
 2531       private boolean replaceNamedDestination(PdfObject obj, HashMap names) {
 2532           obj = getPdfObject(obj);
 2533           int objIdx = lastXrefPartial;
 2534           releaseLastXrefPartial();
 2535           if (obj != null && obj.isDictionary()) {
 2536               PdfObject ob2 = getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.DEST));
 2537               String name = null;
 2538               if (ob2 != null) {
 2539                   if (ob2.isName())
 2540                       name = PdfName.decodeName(ob2.toString());
 2541                   else if (ob2.isString())
 2542                       name = ob2.toString();
 2543                   PdfArray dest = (PdfArray)names.get(name);
 2544                   if (dest != null) {
 2545                       ((PdfDictionary)obj).put(PdfName.DEST, dest);
 2546                       setXrefPartialObject(objIdx, obj);
 2547                       return true;
 2548                   }
 2549               }
 2550               else if ((ob2 = getPdfObject(((PdfDictionary)obj).get(PdfName.A))) != null) {
 2551                   int obj2Idx = lastXrefPartial;
 2552                   releaseLastXrefPartial();
 2553                   PdfDictionary dic = (PdfDictionary)ob2;
 2554                   PdfName type = (PdfName)getPdfObjectRelease(dic.get(PdfName.S));
 2555                   if (PdfName.GOTO.equals(type)) {
 2556                       PdfObject ob3 = getPdfObjectRelease(dic.get(PdfName.D));
 2557                       if (ob3 != null) {
 2558                           if (ob3.isName())
 2559                               name = PdfName.decodeName(ob3.toString());
 2560                           else if (ob3.isString())
 2561                               name = ob3.toString();
 2562                       }
 2563                       PdfArray dest = (PdfArray)names.get(name);
 2564                       if (dest != null) {
 2565                           dic.put(PdfName.D, dest);
 2566                           setXrefPartialObject(obj2Idx, ob2);
 2567                           setXrefPartialObject(objIdx, obj);
 2568                           return true;
 2569                       }
 2570                   }
 2571               }
 2572           }
 2573           return false;
 2574       }
 2575   
 2576       /**
 2577        * Removes all the fields from the document.
 2578        */
 2579       public void removeFields() {
 2580           pageRefs.resetReleasePage();
 2581           for (int k = 1; k <= pageRefs.size(); ++k) {
 2582               PdfDictionary page = pageRefs.getPageN(k);
 2583               PdfArray annots = (PdfArray)getPdfObject(page.get(PdfName.ANNOTS));
 2584               if (annots == null) {
 2585                   pageRefs.releasePage(k);
 2586                   continue;
 2587               }
 2588               ArrayList arr = annots.getArrayList();
 2589               for (int j = 0; j < arr.size(); ++j) {
 2590                   PdfObject obj = getPdfObjectRelease((PdfObject)arr.get(j));
 2591                   if (obj == null || !obj.isDictionary())
 2592                       continue;
 2593                   PdfDictionary annot = (PdfDictionary)obj;
 2594                   if (PdfName.WIDGET.equals(annot.get(PdfName.SUBTYPE)))
 2595                       arr.remove(j--);
 2596               }
 2597               if (arr.isEmpty())
 2598                   page.remove(PdfName.ANNOTS);
 2599               else
 2600                   pageRefs.releasePage(k);
 2601           }
 2602           catalog.remove(PdfName.ACROFORM);
 2603           pageRefs.resetReleasePage();
 2604       }
 2605   
 2606       /**
 2607        * Removes all the annotations and fields from the document.
 2608        */
 2609       public void removeAnnotations() {
 2610           pageRefs.resetReleasePage();
 2611           for (int k = 1; k <= pageRefs.size(); ++k) {
 2612               PdfDictionary page = pageRefs.getPageN(k);
 2613               if (page.get(PdfName.ANNOTS) == null)
 2614                   pageRefs.releasePage(k);
 2615               else
 2616                   page.remove(PdfName.ANNOTS);
 2617           }
 2618           catalog.remove(PdfName.ACROFORM);
 2619           pageRefs.resetReleasePage();
 2620       }
 2621       
 2622       public ArrayList getLinks(int page) {
 2623           pageRefs.resetReleasePage();
 2624           ArrayList result = new ArrayList();
 2625           PdfDictionary pageDic = pageRefs.getPageN(page);
 2626           if (pageDic.get(PdfName.ANNOTS) != null) {
 2627               PdfArray annots = (PdfArray)getPdfObject(pageDic.get(PdfName.ANNOTS));
 2628               ArrayList arr = annots.getArrayList();
 2629               for (int j = 0; j < arr.size(); ++j) {
 2630                   PdfDictionary annot = (PdfDictionary)getPdfObjectRelease((PdfObject)arr.get(j));
 2631                 
 2632                   if (PdfName.LINK.equals(annot.get(PdfName.SUBTYPE))) {
 2633                   	result.add(new PdfAnnotation.PdfImportedLink(annot));
 2634                   }
 2635               }
 2636           }
 2637       	pageRefs.releasePage(page);
 2638           pageRefs.resetReleasePage();
 2639           return result;
 2640       }
 2641   
 2642       private void iterateBookmarks(PdfObject outlineRef, HashMap names) {
 2643           while (outlineRef != null) {
 2644               replaceNamedDestination(outlineRef, names);
 2645               PdfDictionary outline = (PdfDictionary)getPdfObjectRelease(outlineRef);
 2646               PdfObject first = outline.get(PdfName.FIRST);
 2647               if (first != null) {
 2648                   iterateBookmarks(first, names);
 2649               }
 2650               outlineRef = outline.get(PdfName.NEXT);
 2651           }
 2652       }
 2653   
 2654       /** Replaces all the local named links with the actual destinations. */
 2655       public void consolidateNamedDestinations() {
 2656           if (consolidateNamedDestinations)
 2657               return;
 2658           consolidateNamedDestinations = true;
 2659           HashMap names = getNamedDestination();
 2660           if (names.isEmpty())
 2661               return;
 2662           for (int k = 1; k <= pageRefs.size(); ++k) {
 2663               PdfDictionary page = pageRefs.getPageN(k);
 2664               PdfObject annotsRef;
 2665               PdfArray annots = (PdfArray)getPdfObject(annotsRef = page.get(PdfName.ANNOTS));
 2666               int annotIdx = lastXrefPartial;
 2667               releaseLastXrefPartial();
 2668               if (annots == null) {
 2669                   pageRefs.releasePage(k);
 2670                   continue;
 2671               }
 2672               ArrayList list = annots.getArrayList();
 2673               boolean commitAnnots = false;
 2674               for (int an = 0; an < list.size(); ++an) {
 2675                   PdfObject objRef = (PdfObject)list.get(an);
 2676                   if (replaceNamedDestination(objRef, names) && !objRef.isIndirect())
 2677                       commitAnnots = true;
 2678               }
 2679               if (commitAnnots)
 2680                   setXrefPartialObject(annotIdx,  annots);
 2681               if (!commitAnnots || annotsRef.isIndirect())
 2682                   pageRefs.releasePage(k);
 2683           }
 2684           PdfDictionary outlines = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.OUTLINES));
 2685           if (outlines == null)
 2686               return;
 2687           iterateBookmarks(outlines.get(PdfName.FIRST), names);
 2688       }
 2689   
 2690       protected static PdfDictionary duplicatePdfDictionary(PdfDictionary original, PdfDictionary copy, PdfReader newReader) {
 2691           if (copy == null)
 2692               copy = new PdfDictionary();
 2693           for (Iterator it = original.getKeys().iterator(); it.hasNext();) {
 2694               PdfName key = (PdfName)it.next();
 2695               copy.put(key, duplicatePdfObject(original.get(key), newReader));
 2696           }
 2697           return copy;
 2698       }
 2699   
 2700       protected static PdfObject duplicatePdfObject(PdfObject original, PdfReader newReader) {
 2701           if (original == null)
 2702               return null;
 2703           switch (original.type()) {
 2704               case PdfObject.DICTIONARY: {
 2705                   return duplicatePdfDictionary((PdfDictionary)original, null, newReader);
 2706               }
 2707               case PdfObject.STREAM: {
 2708                   PRStream org = (PRStream)original;
 2709                   PRStream stream = new PRStream(org, null, newReader);
 2710                   duplicatePdfDictionary(org, stream, newReader);
 2711                   return stream;
 2712               }
 2713               case PdfObject.ARRAY: {
 2714                   ArrayList list = ((PdfArray)original).getArrayList();
 2715                   PdfArray arr = new PdfArray();
 2716                   for (Iterator it = list.iterator(); it.hasNext();) {
 2717                       arr.add(duplicatePdfObject((PdfObject)it.next(), newReader));
 2718                   }
 2719                   return arr;
 2720               }
 2721               case PdfObject.INDIRECT: {
 2722                   PRIndirectReference org = (PRIndirectReference)original;
 2723                   return new PRIndirectReference(newReader, org.getNumber(), org.getGeneration());
 2724               }
 2725               default:
 2726                   return original;
 2727           }
 2728       }
 2729   
 2730       /**
 2731        * Closes the reader
 2732        */
 2733       public void close() {
 2734           if (!partial)
 2735               return;
 2736           try {
 2737               tokens.close();
 2738           }
 2739           catch (IOException e) {
 2740               throw new ExceptionConverter(e);
 2741           }
 2742       }
 2743   
 2744       protected void removeUnusedNode(PdfObject obj, boolean hits[]) {
 2745           Stack state = new Stack();
 2746           state.push(obj);
 2747           while (!state.empty()) {
 2748               Object current = state.pop();
 2749               if (current == null)
 2750                   continue;
 2751               ArrayList ar = null;
 2752               PdfDictionary dic = null;
 2753               PdfName[] keys = null;
 2754               Object[] objs = null;
 2755               int idx = 0;
 2756               if (current instanceof PdfObject) {
 2757                   obj = (PdfObject)current;
 2758                   switch (obj.type()) {
 2759                       case PdfObject.DICTIONARY:
 2760                       case PdfObject.STREAM:
 2761                           dic = (PdfDictionary)obj;
 2762                           keys = new PdfName[dic.size()];
 2763                           dic.getKeys().toArray(keys);
 2764                           break;
 2765                       case PdfObject.ARRAY:
 2766                            ar = ((PdfArray)obj).getArrayList();
 2767                            break;
 2768                       case PdfObject.INDIRECT:
 2769                           PRIndirectReference ref = (PRIndirectReference)obj;
 2770                           int num = ref.getNumber();
 2771                           if (!hits[num]) {
 2772                               hits[num] = true;
 2773                               state.push(getPdfObjectRelease(ref));
 2774                           }
 2775                           continue;
 2776                       default:
 2777                           continue;
 2778                   }
 2779               }
 2780               else {
 2781                   objs = (Object[])current;
 2782                   if (objs[0] instanceof ArrayList) {
 2783                       ar = (ArrayList)objs[0];
 2784                       idx = ((Integer)objs[1]).intValue();
 2785                   }
 2786                   else {
 2787                       keys = (PdfName[])objs[0];
 2788                       dic = (PdfDictionary)objs[1];
 2789                       idx = ((Integer)objs[2]).intValue();
 2790                   }
 2791               }
 2792               if (ar != null) {
 2793                   for (int k = idx; k < ar.size(); ++k) {
 2794                       PdfObject v = (PdfObject)ar.get(k);
 2795                       if (v.isIndirect()) {
 2796                           int num = ((PRIndirectReference)v).getNumber();
 2797                           if (num >= xrefObj.size() || (!partial && xrefObj.get(num) == null)) {
 2798                               ar.set(k, PdfNull.PDFNULL);
 2799                               continue;
 2800                           }
 2801                       }
 2802                       if (objs == null)
 2803                           state.push(new Object[]{ar, new Integer(k + 1)});
 2804                       else {
 2805                           objs[1] = new Integer(k + 1);
 2806                           state.push(objs);
 2807                       }
 2808                       state.push(v);
 2809                       break;
 2810                   }
 2811               }
 2812               else {
 2813                   for (int k = idx; k < keys.length; ++k) {
 2814                       PdfName key = keys[k];
 2815                       PdfObject v = dic.get(key);
 2816                       if (v.isIndirect()) {
 2817                           int num = ((PRIndirectReference)v).getNumber();
 2818                           if (num >= xrefObj.size() || (!partial && xrefObj.get(num) == null)) {
 2819                               dic.put(key, PdfNull.PDFNULL);
 2820                               continue;
 2821                           }
 2822                       }
 2823                       if (objs == null)
 2824                           state.push(new Object[]{keys, dic, new Integer(k + 1)});
 2825                       else {
 2826                           objs[2] = new Integer(k + 1);
 2827                           state.push(objs);
 2828                       }
 2829                       state.push(v);
 2830                       break;
 2831                   }
 2832               }
 2833           }
 2834       }
 2835           
 2836       /** Removes all the unreachable objects.
 2837        * @return the number of indirect objects removed
 2838        */
 2839       public int removeUnusedObjects() {
 2840           boolean hits[] = new boolean[xrefObj.size()];
 2841           removeUnusedNode(trailer, hits);
 2842           int total = 0;
 2843           if (partial) {
 2844               for (int k = 1; k < hits.length; ++k) {
 2845                   if (!hits[k]) {
 2846                       xref[k * 2] = -1;
 2847                       xref[k * 2 + 1] = 0;
 2848                       xrefObj.set(k, null);
 2849                       ++total;
 2850                   }
 2851               }
 2852           }
 2853           else {
 2854               for (int k = 1; k < hits.length; ++k) {
 2855                   if (!hits[k]) {
 2856                       xrefObj.set(k, null);
 2857                       ++total;
 2858                   }
 2859               }
 2860           }
 2861           return total;
 2862       }
 2863   
 2864       /** Gets a read-only version of <CODE>AcroFields</CODE>.
 2865        * @return a read-only version of <CODE>AcroFields</CODE>
 2866        */
 2867       public AcroFields getAcroFields() {
 2868           return new AcroFields(this, null);
 2869       }
 2870   
 2871       /**
 2872        * Gets the global document JavaScript.
 2873        * @param file the document file
 2874        * @throws IOException on error
 2875        * @return the global document JavaScript
 2876        */
 2877       public String getJavaScript(RandomAccessFileOrArray file) throws IOException {
 2878           PdfDictionary names = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.NAMES));
 2879           if (names == null)
 2880               return null;
 2881           PdfDictionary js = (PdfDictionary)getPdfObjectRelease(names.get(PdfName.JAVASCRIPT));
 2882           if (js == null)
 2883               return null;
 2884           HashMap jscript = PdfNameTree.readTree(js);
 2885           String sortedNames[] = new String[jscript.size()];
 2886           sortedNames = (String[])jscript.keySet().toArray(sortedNames);
 2887           Arrays.sort(sortedNames);
 2888           StringBuffer buf = new StringBuffer();
 2889           for (int k = 0; k < sortedNames.length; ++k) {
 2890               PdfDictionary j = (PdfDictionary)getPdfObjectRelease((PdfIndirectReference)jscript.get(sortedNames[k]));
 2891               if (j == null)
 2892                   continue;
 2893               PdfObject obj = getPdfObjectRelease(j.get(PdfName.JS));
 2894               if (obj != null) {
 2895                   if (obj.isString())
 2896                       buf.append(((PdfString)obj).toUnicodeString()).append('\n');
 2897                   else if (obj.isStream()) {
 2898                       byte bytes[] = getStreamBytes((PRStream)obj, file);
 2899                       if (bytes.length >= 2 && bytes[0] == (byte)254 && bytes[1] == (byte)255)
 2900                           buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_UNICODE));
 2901                       else
 2902                           buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_PDFDOCENCODING));
 2903                       buf.append('\n');
 2904                   }
 2905               }
 2906           }
 2907           return buf.toString();
 2908       }
 2909   
 2910       /**
 2911        * Gets the global document JavaScript.
 2912        * @throws IOException on error
 2913        * @return the global document JavaScript
 2914        */
 2915       public String getJavaScript() throws IOException {
 2916           RandomAccessFileOrArray rf = getSafeFile();
 2917           try {
 2918               rf.reOpen();
 2919               return getJavaScript(rf);
 2920           }
 2921           finally {
 2922               try{rf.close();}catch(Exception e){}
 2923           }
 2924       }
 2925   
 2926       /**
 2927        * Selects the pages to keep in the document. The pages are described as
 2928        * ranges. The page ordering can be changed but
 2929        * no page repetitions are allowed. Note that it may be very slow in partial mode.
 2930        * @param ranges the comma separated ranges as described in {@link SequenceList}
 2931        */
 2932       public void selectPages(String ranges) {
 2933           selectPages(SequenceList.expand(ranges, getNumberOfPages()));
 2934       }
 2935   
 2936       /**
 2937        * Selects the pages to keep in the document. The pages are described as a
 2938        * <CODE>List</CODE> of <CODE>Integer</CODE>. The page ordering can be changed but
 2939        * no page repetitions are allowed. Note that it may be very slow in partial mode.
 2940        * @param pagesToKeep the pages to keep in the document
 2941        */
 2942       public void selectPages(List pagesToKeep) {
 2943           pageRefs.selectPages(pagesToKeep);
 2944           removeUnusedObjects();
 2945       }
 2946   
 2947       /** Sets the viewer preferences as the sum of several constants.
 2948        * @param preferences the viewer preferences
 2949        * @see PdfViewerPreferences#setViewerPreferences
 2950        */
 2951       public void setViewerPreferences(int preferences) {
 2952       	this.viewerPreferences.setViewerPreferences(preferences);
 2953           setViewerPreferences(this.viewerPreferences);
 2954       }
 2955       
 2956       /** Adds a viewer preference
 2957        * @param key a key for a viewer preference
 2958        * @param value	a value for the viewer preference
 2959        * @see PdfViewerPreferences#addViewerPreference
 2960        */
 2961       
 2962       public void addViewerPreference(PdfName key, PdfObject value) {
 2963       	this.viewerPreferences.addViewerPreference(key, value);
 2964           setViewerPreferences(this.viewerPreferences);
 2965       }
 2966       
 2967       void setViewerPreferences(PdfViewerPreferencesImp vp) {
 2968       	vp.addToCatalog(catalog);
 2969       }
 2970   
 2971       /**
 2972        * @return an int that contains the Viewer Preferences.
 2973        */
 2974       public int getSimpleViewerPreferences() {
 2975       	return PdfViewerPreferencesImp.getViewerPreferences(catalog).getPageLayoutAndMode();
 2976       }
 2977   
 2978       /**
 2979        * Getter for property appendable.
 2980        * @return Value of property appendable.
 2981        */
 2982       public boolean isAppendable() {
 2983           return this.appendable;
 2984       }
 2985   
 2986       /**
 2987        * Setter for property appendable.
 2988        * @param appendable New value of property appendable.
 2989        */
 2990       public void setAppendable(boolean appendable) {
 2991           this.appendable = appendable;
 2992           if (appendable)
 2993               getPdfObject(trailer.get(PdfName.ROOT));
 2994       }
 2995   
 2996       /**
 2997        * Getter for property newXrefType.
 2998        * @return Value of property newXrefType.
 2999        */
 3000       public boolean isNewXrefType() {
 3001           return newXrefType;
 3002       }
 3003   
 3004       /**
 3005        * Getter for property fileLength.
 3006        * @return Value of property fileLength.
 3007        */
 3008       public int getFileLength() {
 3009           return fileLength;
 3010       }
 3011   
 3012       /**
 3013        * Getter for property hybridXref.
 3014        * @return Value of property hybridXref.
 3015        */
 3016       public boolean isHybridXref() {
 3017           return hybridXref;
 3018       }
 3019   
 3020       static class PageRefs {
 3021           private PdfReader reader;
 3022           private IntHashtable refsp;
 3023           private ArrayList refsn;
 3024           private ArrayList pageInh;
 3025           private int lastPageRead = -1;
 3026           private int sizep;
 3027           private boolean keepPages;
 3028   
 3029           private PageRefs(PdfReader reader) throws IOException {
 3030               this.reader = reader;
 3031               if (reader.partial) {
 3032                   refsp = new IntHashtable();
 3033                   PdfNumber npages = (PdfNumber)PdfReader.getPdfObjectRelease(reader.rootPages.get(PdfName.COUNT));
 3034                   sizep = npages.intValue();
 3035               }
 3036               else {
 3037                   readPages();
 3038               }
 3039           }
 3040   
 3041           PageRefs(PageRefs other, PdfReader reader) {
 3042               this.reader = reader;
 3043               this.sizep = other.sizep;
 3044               if (other.refsn != null) {
 3045                   refsn = new ArrayList(other.refsn);
 3046                   for (int k = 0; k < refsn.size(); ++k) {
 3047                       refsn.set(k, duplicatePdfObject((PdfObject)refsn.get(k), reader));
 3048                   }
 3049               }
 3050               else
 3051                   this.refsp = (IntHashtable)other.refsp.clone();
 3052           }
 3053   
 3054           int size() {
 3055               if (refsn != null)
 3056                   return refsn.size();
 3057               else
 3058                   return sizep;
 3059           }
 3060   
 3061           void readPages() throws IOException {
 3062               if (refsn != null)
 3063                   return;
 3064               refsp = null;
 3065               refsn = new ArrayList();
 3066               pageInh = new ArrayList();
 3067               iteratePages((PRIndirectReference)reader.catalog.get(PdfName.PAGES));
 3068               pageInh = null;
 3069               reader.rootPages.put(PdfName.COUNT, new PdfNumber(refsn.size()));
 3070           }
 3071   
 3072           void reReadPages() throws IOException {
 3073               refsn = null;
 3074               readPages();
 3075           }
 3076   
 3077           /** Gets the dictionary that represents a page.
 3078            * @param pageNum the page number. 1 is the first
 3079            * @return the page dictionary
 3080            */
 3081           public PdfDictionary getPageN(int pageNum) {
 3082               PRIndirectReference ref = getPageOrigRef(pageNum);
 3083               return (PdfDictionary)PdfReader.getPdfObject(ref);
 3084           }
 3085   
 3086           /**
 3087            * @param pageNum
 3088            * @return a dictionary object
 3089            */
 3090           public PdfDictionary getPageNRelease(int pageNum) {
 3091               PdfDictionary page = getPageN(pageNum);
 3092               releasePage(pageNum);
 3093               return page;
 3094           }
 3095   
 3096           /**
 3097            * @param pageNum
 3098            * @return an indirect reference
 3099            */
 3100           public PRIndirectReference getPageOrigRefRelease(int pageNum) {
 3101               PRIndirectReference ref = getPageOrigRef(pageNum);
 3102               releasePage(pageNum);
 3103               return ref;
 3104           }
 3105   
 3106           /** Gets the page reference to this page.
 3107            * @param pageNum the page number. 1 is the first
 3108            * @return the page reference
 3109            */
 3110           public PRIndirectReference getPageOrigRef(int pageNum) {
 3111               try {
 3112                   --pageNum;
 3113                   if (pageNum < 0 || pageNum >= size())
 3114                       return null;
 3115                   if (refsn != null)
 3116                       return (PRIndirectReference)refsn.get(pageNum);
 3117                   else {
 3118                       int n = refsp.get(pageNum);
 3119                       if (n == 0) {
 3120                           PRIndirectReference ref = getSinglePage(pageNum);
 3121                           if (reader.lastXrefPartial == -1)
 3122                               lastPageRead = -1;
 3123                           else
 3124                               lastPageRead = pageNum;
 3125                           reader.lastXrefPartial = -1;
 3126                           refsp.put(pageNum, ref.getNumber());
 3127                           if (keepPages)
 3128                               lastPageRead = -1;
 3129                           return ref;
 3130                       }
 3131                       else {
 3132                           if (lastPageRead != pageNum)
 3133                               lastPageRead = -1;
 3134                           if (keepPages)
 3135                               lastPageRead = -1;
 3136                           return new PRIndirectReference(reader, n);
 3137                       }
 3138                   }
 3139               }
 3140               catch (Exception e) {
 3141                   throw new ExceptionConverter(e);
 3142               }
 3143           }
 3144   
 3145           void keepPages() {
 3146               if (refsp == null || keepPages)
 3147                   return;
 3148               keepPages = true;
 3149               refsp.clear();
 3150           }
 3151           
 3152           /**
 3153            * @param pageNum
 3154            */
 3155           public void releasePage(int pageNum) {
 3156               if (refsp == null)
 3157                   return;
 3158               --pageNum;
 3159               if (pageNum < 0 || pageNum >= size())
 3160                   return;
 3161               if (pageNum != lastPageRead)
 3162                   return;
 3163               lastPageRead = -1;
 3164               reader.lastXrefPartial = refsp.get(pageNum);
 3165               reader.releaseLastXrefPartial();
 3166               refsp.remove(pageNum);
 3167           }
 3168   
 3169           /**
 3170            *
 3171            */
 3172           public void resetReleasePage() {
 3173               if (refsp == null)
 3174                   return;
 3175               lastPageRead = -1;
 3176           }
 3177   
 3178           void insertPage(int pageNum, PRIndirectReference ref) {
 3179               --pageNum;
 3180               if (refsn != null) {
 3181                   if (pageNum >= refsn.size())
 3182                       refsn.add(ref);
 3183                   else
 3184                       refsn.add(pageNum, ref);
 3185               }
 3186               else {
 3187                   ++sizep;
 3188                   lastPageRead = -1;
 3189                   if (pageNum >= size()) {
 3190                       refsp.put(size(), ref.getNumber());
 3191                   }
 3192                   else {
 3193                       IntHashtable refs2 = new IntHashtable((refsp.size() + 1) * 2);
 3194                       for (Iterator it = refsp.getEntryIterator(); it.hasNext();) {
 3195                           IntHashtable.Entry entry = (IntHashtable.Entry)it.next();
 3196                           int p = entry.getKey();
 3197                           refs2.put(p >= pageNum ? p + 1 : p, entry.getValue());
 3198                       }
 3199                       refs2.put(pageNum, ref.getNumber());
 3200                       refsp = refs2;
 3201                   }
 3202               }
 3203           }
 3204   
 3205           private void pushPageAttributes(PdfDictionary nodePages) {
 3206               PdfDictionary dic = new PdfDictionary();
 3207               if (!pageInh.isEmpty()) {
 3208                   dic.putAll((PdfDictionary)pageInh.get(pageInh.size() - 1));
 3209               }
 3210               for (int k = 0; k < pageInhCandidates.length; ++k) {
 3211                   PdfObject obj = nodePages.get(pageInhCandidates[k]);
 3212                   if (obj != null)
 3213                       dic.put(pageInhCandidates[k], obj);
 3214               }
 3215               pageInh.add(dic);
 3216           }
 3217   
 3218           private void popPageAttributes() {
 3219               pageInh.remove(pageInh.size() - 1);
 3220           }
 3221   
 3222           private void iteratePages(PRIndirectReference rpage) throws IOException {
 3223               PdfDictionary page = (PdfDictionary)getPdfObject(rpage);
 3224               PdfArray kidsPR = (PdfArray)getPdfObject(page.get(PdfName.KIDS));
 3225               if (kidsPR == null) {
 3226                   page.put(PdfName.TYPE, PdfName.PAGE);
 3227                   PdfDictionary dic = (PdfDictionary)pageInh.get(pageInh.size() - 1);
 3228                   PdfName key;
 3229                   for (Iterator i = dic.getKeys().iterator(); i.hasNext();) {
 3230                       key = (PdfName)i.next();
 3231                       if (page.get(key) == null)
 3232                           page.put(key, dic.get(key));
 3233                   }
 3234                   if (page.get(PdfName.MEDIABOX) == null) {
 3235                       PdfArray arr = new PdfArray(new float[]{0,0,PageSize.LETTER.getRight(),PageSize.LETTER.getTop()});
 3236                       page.put(PdfName.MEDIABOX, arr);
 3237                   }
 3238                   refsn.add(rpage);
 3239               }
 3240               else {
 3241                   page.put(PdfName.TYPE, PdfName.PAGES);
 3242                   pushPageAttributes(page);
 3243                   ArrayList kids = kidsPR.getArrayList();
 3244                   for (int k = 0; k < kids.size(); ++k){
 3245                       PdfObject obj = (PdfObject)kids.get(k);
 3246                       if (!obj.isIndirect()) {
 3247                           while (k < kids.size())
 3248                               kids.remove(k);
 3249                           break;
 3250                       }
 3251                       iteratePages((PRIndirectReference)obj);
 3252                   }
 3253                   popPageAttributes();
 3254               }
 3255           }
 3256   
 3257           protected PRIndirectReference getSinglePage(int n) {
 3258               PdfDictionary acc = new PdfDictionary();
 3259               PdfDictionary top = reader.rootPages;
 3260               int base = 0;
 3261               while (true) {
 3262                   for (int k = 0; k < pageInhCandidates.length; ++k) {
 3263                       PdfObject obj = top.get(pageInhCandidates[k]);
 3264                       if (obj != null)
 3265                           acc.put(pageInhCandidates[k], obj);
 3266                   }
 3267                   PdfArray kids = (PdfArray)PdfReader.getPdfObjectRelease(top.get(PdfName.KIDS));
 3268                   for (Iterator it = kids.listIterator(); it.hasNext();) {
 3269                       PRIndirectReference ref = (PRIndirectReference)it.next();
 3270                       PdfDictionary dic = (PdfDictionary)getPdfObject(ref);
 3271                       int last = reader.lastXrefPartial;
 3272                       PdfObject count = getPdfObjectRelease(dic.get(PdfName.COUNT));
 3273                       reader.lastXrefPartial = last;
 3274                       int acn = 1;
 3275                       if (count != null && count.type() == PdfObject.NUMBER)
 3276                           acn = ((PdfNumber)count).intValue();
 3277                       if (n < base + acn) {
 3278                           if (count == null) {
 3279                               dic.mergeDifferent(acc);
 3280                               return ref;
 3281                           }
 3282                           reader.releaseLastXrefPartial();
 3283                           top = dic;
 3284                           break;
 3285                       }
 3286                       reader.releaseLastXrefPartial();
 3287                       base += acn;
 3288                   }
 3289               }
 3290           }
 3291   
 3292           private void selectPages(List pagesToKeep) {
 3293               IntHashtable pg = new IntHashtable();
 3294               ArrayList finalPages = new ArrayList();
 3295               int psize = size();
 3296               for (Iterator it = pagesToKeep.iterator(); it.hasNext();) {
 3297                   Integer pi = (Integer)it.next();
 3298                   int p = pi.intValue();
 3299                   if (p >= 1 && p <= psize && pg.put(p, 1) == 0)
 3300                       finalPages.add(pi);
 3301               }
 3302               if (reader.partial) {
 3303                   for (int k = 1; k <= psize; ++k) {
 3304                       getPageOrigRef(k);
 3305                       resetReleasePage();
 3306                   }
 3307               }
 3308               PRIndirectReference parent = (PRIndirectReference)reader.catalog.get(PdfName.PAGES);
 3309               PdfDictionary topPages = (PdfDictionary)PdfReader.getPdfObject(parent);
 3310               ArrayList newPageRefs = new ArrayList(finalPages.size());
 3311               PdfArray kids = new PdfArray();
 3312               for (int k = 0; k < finalPages.size(); ++k) {
 3313                   int p = ((Integer)finalPages.get(k)).intValue();
 3314                   PRIndirectReference pref = getPageOrigRef(p);
 3315                   resetReleasePage();
 3316                   kids.add(pref);
 3317                   newPageRefs.add(pref);
 3318                   getPageN(p).put(PdfName.PARENT, parent);
 3319               }
 3320               AcroFields af = reader.getAcroFields();
 3321               boolean removeFields = (af.getFields().size() > 0);
 3322               for (int k = 1; k <= psize; ++k) {
 3323                   if (!pg.containsKey(k)) {
 3324                       if (removeFields)
 3325                           af.removeFieldsFromPage(k);
 3326                       PRIndirectReference pref = getPageOrigRef(k);
 3327                       int nref = pref.getNumber();
 3328                       reader.xrefObj.set(nref, null);
 3329                       if (reader.partial) {
 3330                           reader.xref[nref * 2] = -1;
 3331                           reader.xref[nref * 2 + 1] = 0;
 3332                       }
 3333                   }
 3334               }
 3335               topPages.put(PdfName.COUNT, new PdfNumber(finalPages.size()));
 3336               topPages.put(PdfName.KIDS, kids);
 3337               refsp = null;
 3338               refsn = newPageRefs;
 3339           }
 3340       }
 3341       
 3342       PdfIndirectReference getCryptoRef() {
 3343           if (cryptoRef == null)
 3344               return null;
 3345           return new PdfIndirectReference(0, cryptoRef.getNumber(), cryptoRef.getGeneration());
 3346       }
 3347       
 3348       /**
 3349        * Removes any usage rights that this PDF may have. Only Adobe can grant usage rights
 3350        * and any PDF modification with iText will invalidate them. Invalidated usage rights may
 3351        * confuse Acrobat and it's advisable to remove them altogether.
 3352        */
 3353       public void removeUsageRights() {
 3354           PdfDictionary perms = (PdfDictionary)getPdfObject(catalog.get(PdfName.PERMS));
 3355           if (perms == null)
 3356               return;
 3357           perms.remove(PdfName.UR);
 3358           perms.remove(PdfName.UR3);
 3359           if (perms.size() == 0)
 3360               catalog.remove(PdfName.PERMS);
 3361       }
 3362       
 3363       /**
 3364        * Gets the certification level for this document. The return values can be <code>PdfSignatureAppearance.NOT_CERTIFIED</code>, 
 3365        * <code>PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED</code>,
 3366        * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING</code> and
 3367        * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS</code>.
 3368        * <p>
 3369        * No signature validation is made, use the methods available for that in <CODE>AcroFields</CODE>.
 3370        * </p>
 3371        * @return gets the certification level for this document
 3372        */
 3373       public int getCertificationLevel() {
 3374           PdfDictionary dic = (PdfDictionary)getPdfObject(catalog.get(PdfName.PERMS));
 3375           if (dic == null)
 3376               return PdfSignatureAppearance.NOT_CERTIFIED;
 3377           dic = (PdfDictionary)getPdfObject(dic.get(PdfName.DOCMDP));
 3378           if (dic == null)
 3379               return PdfSignatureAppearance.NOT_CERTIFIED;
 3380           PdfArray arr = (PdfArray)getPdfObject(dic.get(PdfName.REFERENCE));
 3381           if (arr == null || arr.size() == 0)
 3382               return PdfSignatureAppearance.NOT_CERTIFIED;
 3383           dic = (PdfDictionary)getPdfObject((PdfObject)(arr.getArrayList().get(0)));
 3384           if (dic == null)
 3385               return PdfSignatureAppearance.NOT_CERTIFIED;
 3386           dic = (PdfDictionary)getPdfObject(dic.get(PdfName.TRANSFORMPARAMS));
 3387           if (dic == null)
 3388               return PdfSignatureAppearance.NOT_CERTIFIED;
 3389           PdfNumber p = (PdfNumber)getPdfObject(dic.get(PdfName.P));
 3390           if (p == null)
 3391               return PdfSignatureAppearance.NOT_CERTIFIED;
 3392           return p.intValue();
 3393       } 
 3394       
 3395       /**
 3396        * Checks if the document was opened with the owner password so that the end application
 3397        * can decide what level of access restrictions to apply. If the document is not encrypted
 3398        * it will return <CODE>true</CODE>.
 3399        * @return <CODE>true</CODE> if the document was opened with the owner password or if it's not encrypted,
 3400        * <CODE>false</CODE> if the document was opened with the user password
 3401        */
 3402       public final boolean isOpenedWithFullPermissions() {
 3403           return !encrypted || ownerPasswordUsed;
 3404       }
 3405       
 3406       public int getCryptoMode() {
 3407       	if (decrypt == null) 
 3408       		return -1;
 3409       	else 
 3410       		return decrypt.getCryptoMode();
 3411       }
 3412       
 3413       public boolean isMetadataEncrypted() {
 3414       	if (decrypt == null) 
 3415       		return false; 
 3416       	else 
 3417       		return decrypt.isMetadataEncrypted();
 3418       }
 3419       
 3420       public byte[] computeUserPassword() {
 3421       	if (!encrypted || !ownerPasswordUsed) return null;
 3422       	return decrypt.computeUserPassword(password);
 3423       }
 3424   }

Save This Page
Home » iText-src-2.1.3 » com.lowagie » text » pdf » [javadoc | source]