using System;
using System.Collections;
using System.Security.Cryptography;
using System.Net;
using System.Text;
using System.IO;
using iTextSharp.text;
using iTextSharp.text.pdf.intern;
using iTextSharp.text.pdf.interfaces;
using System.util;
using System.util.zlib;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Cms;
using Org.BouncyCastle.X509;
/*
 * $Id: PdfReader.cs,v 1.28 2007/02/09 15:34:38 psoares33 Exp $
 * $Name:  $
 *
 * Copyright 2001, 2002 Paulo Soares
 *
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the License.
 *
 * The Original Code is 'iText, a free JAVA-PDF library'.
 *
 * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
 * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
 * All Rights Reserved.
 * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
 * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
 *
 * Contributor(s): all the names of the contributors are added in the source code
 * where applicable.
 *
 * Alternatively, the contents of this file may be used under the terms of the
 * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
 * provisions of LGPL are applicable instead of those above.  If you wish to
 * allow use of your version of this file only under the terms of the LGPL
 * License and not to allow others to use your version of this file under
 * the MPL, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the LGPL.
 * If you do not delete the provisions above, a recipient may use your version
 * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the MPL as stated above or under the terms of the GNU
 * Library General Public License as published by the Free Software Foundation;
 * either version 2 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
 * details.
 *
 * If you didn't download this code from the following link, you should check if
 * you aren't using an obsolete version:
 * http://www.lowagie.com/iText/
 */
namespace iTextSharp.text.pdf {
    /** Reads a PDF document.
    * @author Paulo Soares (psoares@consiste.pt)
    * @author Kazuya Ujihara
    */
    public class PdfReader : IPdfViewerPreferences {
        
        static PdfName[] pageInhCandidates = {
            PdfName.MEDIABOX, PdfName.ROTATE, PdfName.RESOURCES, PdfName.CROPBOX
        };
        static byte[] endstream = PdfEncodings.ConvertToBytes("endstream", null);
        static byte[] endobj = PdfEncodings.ConvertToBytes("endobj", null);
        protected internal PRTokeniser tokens;
        // Each xref pair is a position
        // type 0 -> -1, 0
        // type 1 -> offset, 0
        // type 2 -> index, obj num
        protected internal int[] xref;
        protected internal Hashtable objStmMark;
        protected internal IntHashtable objStmToOffset;
        protected internal bool newXrefType;
        private ArrayList xrefObj;
        PdfDictionary rootPages;
        protected internal PdfDictionary trailer;
        //protected internal ArrayList pages;
        protected internal PdfDictionary catalog;
        protected internal PageRefs pageRefs;
        protected internal PRAcroForm acroForm = null;
        protected internal bool acroFormParsed = false;
        protected internal ArrayList pageInh;
        protected internal bool encrypted = false;
        protected internal bool rebuilt = false;
        protected internal int freeXref;
        protected internal bool tampered = false;
        protected internal int lastXref;
        protected internal int eofPos;
        protected internal char pdfVersion;
        protected internal PdfEncryption decrypt;
        protected internal byte[] password = null; //added by ujihara for decryption
        protected ICipherParameters certificateKey = null; //added by Aiken Sam for certificate decryption
        protected X509Certificate certificate = null; //added by Aiken Sam for certificate decryption
        protected internal ArrayList strings = new ArrayList();
        protected internal bool sharedStreams = true;
        protected internal bool consolidateNamedDestinations = false;
        protected internal int rValue;
        protected internal int pValue;
        private int objNum;
        private int objGen;
        private int fileLength;
        private bool hybridXref;
        private int lastXrefPartial = -1;
        private bool partial;
        private PRIndirectReference cryptoRef;
        private PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
        /**
        * Holds value of property appendable.
        */
        private bool appendable;
        
        protected internal PdfReader() {
        }
        
        /** Reads and parses a PDF document.
        * @param filename the file name of the document
        * @throws IOException on error
        */
        public PdfReader(String filename) : this(filename, null) {
        }
        
        /** Reads and parses a PDF document.
        * @param filename the file name of the document
        * @param ownerPassword the password to read the document
        * @throws IOException on error
        */    
        public PdfReader(String filename, byte[] ownerPassword) {
            password = ownerPassword;
            tokens = new PRTokeniser(filename);
            ReadPdf();
        }
        
        /** Reads and parses a PDF document.
        * @param pdfIn the byte array with the document
        * @throws IOException on error
        */
        public PdfReader(byte[] pdfIn) : this(pdfIn, null) {
        }
        
        /** Reads and parses a PDF document.
        * @param pdfIn the byte array with the document
        * @param ownerPassword the password to read the document
        * @throws IOException on error
        */
        public PdfReader(byte[] pdfIn, byte[] ownerPassword) {
            password = ownerPassword;
            tokens = new PRTokeniser(pdfIn);
            ReadPdf();
        }
        
        /** Reads and parses a PDF document.
        * @param filename the file name of the document
        * @param certificate the certificate to read the document
        * @param certificateKey the private key of the certificate
        * @param certificateKeyProvider the security provider for certificateKey
        * @throws IOException on error
        */
        public PdfReader(String filename, X509Certificate certificate, ICipherParameters certificateKey) {
            this.certificate = certificate;
            this.certificateKey = certificateKey;
            tokens = new PRTokeniser(filename);
            ReadPdf();
        }        
    /** Reads and parses a PDF document.
        * @param url the Uri of the document
        * @throws IOException on error
        */
        public PdfReader(Uri url) : this(url, null) {
        }
        
        /** Reads and parses a PDF document.
        * @param url the Uri of the document
        * @param ownerPassword the password to read the document
        * @throws IOException on error
        */
        public PdfReader(Uri url, byte[] ownerPassword) {
            password = ownerPassword;
            tokens = new PRTokeniser(new RandomAccessFileOrArray(url));
            ReadPdf();
        }
        
        /**
        * Reads and parses a PDF document.
        * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
        * end but is not closed
        * @param ownerPassword the password to read the document
        * @throws IOException on error
        */
        public PdfReader(Stream isp, byte[] ownerPassword) {
            password = ownerPassword;
            tokens = new PRTokeniser(new RandomAccessFileOrArray(isp));
            ReadPdf();
        }
        
        /**
        * Reads and parses a PDF document.
        * @param isp the <CODE>InputStream</CODE> containing the document. The stream is read to the
        * end but is not closed
        * @throws IOException on error
        */
        public PdfReader(Stream isp) : this(isp, null) {
        }
        
        /**
        * Reads and parses a pdf document. Contrary to the other constructors only the xref is read
        * into memory. The reader is said to be working in "partial" mode as only parts of the pdf
        * are read as needed. The pdf is left open but may be closed at any time with
        * <CODE>PdfReader.Close()</CODE>, reopen is automatic.
        * @param raf the document location
        * @param ownerPassword the password or <CODE>null</CODE> for no password
        * @throws IOException on error
        */    
        public PdfReader(RandomAccessFileOrArray raf, byte[] ownerPassword) {
            password = ownerPassword;
            partial = true;
            tokens = new PRTokeniser(raf);
            ReadPdfPartial();
        }
        
        /** Creates an independent duplicate.
        * @param reader the <CODE>PdfReader</CODE> to duplicate
        */    
        public PdfReader(PdfReader reader) {
            this.appendable = reader.appendable;
            this.consolidateNamedDestinations = reader.consolidateNamedDestinations;
            this.encrypted = reader.encrypted;
            this.rebuilt = reader.rebuilt;
            this.sharedStreams = reader.sharedStreams;
            this.tampered = reader.tampered;
            this.password = reader.password;
            this.pdfVersion = reader.pdfVersion;
            this.eofPos = reader.eofPos;
            this.freeXref = reader.freeXref;
            this.lastXref = reader.lastXref;
            this.tokens = new PRTokeniser(reader.tokens.SafeFile);
            if (reader.decrypt != null)
                this.decrypt = new PdfEncryption(reader.decrypt);
            this.pValue = reader.pValue;
            this.rValue = reader.rValue;
            this.xrefObj = new ArrayList(reader.xrefObj);
            for (int k = 0; k < reader.xrefObj.Count; ++k) {
                this.xrefObj[k] = DuplicatePdfObject((PdfObject)reader.xrefObj[k], this);
            }
            this.pageRefs = new PageRefs(reader.pageRefs, this);
            this.trailer = (PdfDictionary)DuplicatePdfObject(reader.trailer, this);
            this.catalog = (PdfDictionary)GetPdfObject(trailer.Get(PdfName.ROOT));
            this.rootPages = (PdfDictionary)GetPdfObject(catalog.Get(PdfName.PAGES));
            this.fileLength = reader.fileLength;
            this.partial = reader.partial;
            this.hybridXref = reader.hybridXref;
            this.objStmToOffset = reader.objStmToOffset;
            this.xref = reader.xref;
            this.cryptoRef = (PRIndirectReference)DuplicatePdfObject(reader.cryptoRef, this);
        }
                                                                                      
        /** Gets a new file instance of the original PDF
        * document.
        * @return a new file instance of the original PDF document
        */
        public RandomAccessFileOrArray SafeFile {
            get {
                return tokens.SafeFile;
            }
        }
        
        protected internal PdfReaderInstance GetPdfReaderInstance(PdfWriter writer) {
            return new PdfReaderInstance(this, writer);
        }
        
        /** Gets the number of pages in the document.
        * @return the number of pages in the document
        */
        public int NumberOfPages {
            get {
                return pageRefs.Size;
            }
        }
        
        /** Returns the document's catalog. This dictionary is not a copy,
        * any changes will be reflected in the catalog.
        * @return the document's catalog
        */
        public PdfDictionary Catalog {
            get {
                return catalog;
            }
        }
        
        /** Returns the document's acroform, if it has one.
        * @return the document's acroform
        */
        public PRAcroForm AcroForm {
            get {
                if (!acroFormParsed) {
                    acroFormParsed = true;
                    PdfObject form = catalog.Get(PdfName.ACROFORM);
                    if (form != null) {
                        try {
                            acroForm = new PRAcroForm(this);
                            acroForm.ReadAcroForm((PdfDictionary)GetPdfObject(form));
                        }
                        catch {
                            acroForm = null;
                        }
                    }
                }
                return acroForm;
            }
        }
        /**
        * Gets the page rotation. This value can be 0, 90, 180 or 270.
        * @param index the page number. The first page is 1
        * @return the page rotation
        */
        public int GetPageRotation(int index) {
            return GetPageRotation(pageRefs.GetPageNRelease(index));
        }
        
        internal int GetPageRotation(PdfDictionary page) {
            PdfNumber rotate = (PdfNumber)GetPdfObject(page.Get(PdfName.ROTATE));
            if (rotate == null)
                return 0;
            else {
                int n = rotate.IntValue;
                n %= 360;
                return n < 0 ? n + 360 : n;
            }
        }
        /** Gets the page size, taking rotation into account. This
        * is a <CODE>Rectangle</CODE> with the value of the /MediaBox and the /Rotate key.
        * @param index the page number. The first page is 1
        * @return a <CODE>Rectangle</CODE>
        */
        public Rectangle GetPageSizeWithRotation(int index) {
            return GetPageSizeWithRotation(pageRefs.GetPageNRelease(index));
        }
        
        /**
        * Gets the rotated page from a page dictionary.
        * @param page the page dictionary
        * @return the rotated page
        */    
        public Rectangle GetPageSizeWithRotation(PdfDictionary page) {
            Rectangle rect = GetPageSize(page);
            int rotation = GetPageRotation(page);
            while (rotation > 0) {
                rect = rect.Rotate();
                rotation -= 90;
            }
            return rect;
        }
        
        /** Gets the page size without taking rotation into account. This
        * is the value of the /MediaBox key.
        * @param index the page number. The first page is 1
        * @return the page size
        */
        public Rectangle GetPageSize(int index) {
            return GetPageSize(pageRefs.GetPageNRelease(index));
        }
        
        /**
        * Gets the page from a page dictionary
        * @param page the page dictionary
        * @return the page
        */    
        public Rectangle GetPageSize(PdfDictionary page) {
            PdfArray mediaBox = (PdfArray)GetPdfObject(page.Get(PdfName.MEDIABOX));
            return GetNormalizedRectangle(mediaBox);
        }
        
        /** Gets the crop box without taking rotation into account. This
        * is the value of the /CropBox key. The crop box is the part
        * of the document to be displayed or printed. It usually is the same
        * as the media box but may be smaller. If the page doesn't have a crop
        * box the page size will be returned.
        * @param index the page number. The first page is 1
        * @return the crop box
        */
        public Rectangle GetCropBox(int index) {
            PdfDictionary page = pageRefs.GetPageNRelease(index);
            PdfArray cropBox = (PdfArray)GetPdfObjectRelease(page.Get(PdfName.CROPBOX));
            if (cropBox == null)
                return GetPageSize(page);
            return GetNormalizedRectangle(cropBox);
        }
        
        /** Gets the box size. Allowed names are: "crop", "trim", "art", "bleed" and "media".
        * @param index the page number. The first page is 1
        * @param boxName the box name
        * @return the box rectangle or null
        */
        public Rectangle GetBoxSize(int index, String boxName) {
            PdfDictionary page = pageRefs.GetPageNRelease(index);
            PdfArray box = null;
            if (boxName.Equals("trim"))
                box = (PdfArray)GetPdfObjectRelease(page.Get(PdfName.TRIMBOX));
            else if (boxName.Equals("art"))
                box = (PdfArray)GetPdfObjectRelease(page.Get(PdfName.ARTBOX));
            else if (boxName.Equals("bleed"))
                box = (PdfArray)GetPdfObjectRelease(page.Get(PdfName.BLEEDBOX));
            else if (boxName.Equals("crop"))
                box = (PdfArray)GetPdfObjectRelease(page.Get(PdfName.CROPBOX));
            else if (boxName.Equals("media"))
                box = (PdfArray)GetPdfObjectRelease(page.Get(PdfName.MEDIABOX));
            if (box == null)
                return null;
            return GetNormalizedRectangle(box);
        }
        
        /** Returns the content of the document information dictionary as a <CODE>Hashtable</CODE>
        * of <CODE>String</CODE>.
        * @return content of the document information dictionary
        */
        public Hashtable Info {
            get {
                Hashtable map = new Hashtable();
                PdfDictionary info = (PdfDictionary)GetPdfObject(trailer.Get(PdfName.INFO));
                if (info == null)
                    return map;
                foreach (PdfName key in info.Keys) {
                    PdfObject obj = GetPdfObject(info.Get(key));
                    if (obj == null)
                        continue;
                    String value = obj.ToString();
                    switch (obj.Type) {
                        case PdfObject.STRING: {
                            value = ((PdfString)obj).ToUnicodeString();
                            break;
                        }
                        case PdfObject.NAME: {
                            value = PdfName.DecodeName(value);
                            break;
                        }
                    }
                    map[PdfName.DecodeName(key.ToString())] = value;
                }
                return map;
            }
        }
        
        /** Normalizes a <CODE>Rectangle</CODE> so that llx and lly are smaller than urx and ury.
        * @param box the original rectangle
        * @return a normalized <CODE>Rectangle</CODE>
        */    
        public static Rectangle GetNormalizedRectangle(PdfArray box) {
            ArrayList rect = box.ArrayList;
            float llx = ((PdfNumber)rect[0]).FloatValue;
            float lly = ((PdfNumber)rect[1]).FloatValue;
            float urx = ((PdfNumber)rect[2]).FloatValue;
            float ury = ((PdfNumber)rect[3]).FloatValue;
            return new Rectangle(Math.Min(llx, urx), Math.Min(lly, ury),
            Math.Max(llx, urx), Math.Max(lly, ury));
        }
        
        protected internal virtual void ReadPdf() {
            try {
                fileLength = tokens.File.Length;
                pdfVersion = tokens.CheckPdfHeader();
                try {
                    ReadXref();
                }
                catch (Exception e) {
                    try {
                        rebuilt = true;
                        RebuildXref();
                        lastXref = -1;
                    }
                    catch (Exception ne) {
                        throw new IOException("Rebuild failed: " + ne.Message + "; Original message: " + e.Message);
                    }
                }
                try {
                    ReadDocObj();
                }
                catch (Exception ne) {
                    if (rebuilt)
                        throw ne;
                    rebuilt = true;
                    encrypted = false;
                    RebuildXref();
                    lastXref = -1;
                    ReadDocObj();
                }
                
                strings.Clear();
                ReadPages();
                EliminateSharedStreams();
                RemoveUnusedObjects();
            }
            finally {
                try {
                    tokens.Close();
                }
                catch {
                    // empty on purpose
                }
            }
        }
        
        protected internal void ReadPdfPartial() {
            try {
                fileLength = tokens.File.Length;
                pdfVersion = tokens.CheckPdfHeader();
                try {
                    ReadXref();
                }
                catch (Exception e) {
                    try {
                        rebuilt = true;
                        RebuildXref();
                        lastXref = -1;
                    }
                    catch (Exception ne) {
                        throw new IOException("Rebuild failed: " + ne.Message + "; Original message: " + e.Message);
                    }
                }
                ReadDocObjPartial();
                ReadPages();
            }
            catch (IOException e) {
                try{tokens.Close();}catch{}
                throw e;
            }
        }
        
        private bool EqualsArray(byte[] ar1, byte[] ar2, int size) {
            for (int k = 0; k < size; ++k) {
                if (ar1[k] != ar2[k])
                    return false;
            }
            return true;
        }
        
        /**
        * @throws IOException
        */
        private void ReadDecryptedDocObj() {
            if (encrypted)
                return;
            PdfObject encDic = trailer.Get(PdfName.ENCRYPT);
            if (encDic == null || encDic.ToString().Equals("null"))
                return;
            byte[] encryptionKey = null;
       	
            encrypted = true;
            PdfDictionary enc = (PdfDictionary)GetPdfObject(encDic);
            
            String s;
            PdfObject o;
            
            PdfArray documentIDs = (PdfArray)GetPdfObject(trailer.Get(PdfName.ID));
            byte[] documentID = null;
            if (documentIDs != null) {
                o = (PdfObject)documentIDs.ArrayList[0];
                strings.Remove(o);
                s = o.ToString();
                documentID = DocWriter.GetISOBytes(s);
                if (documentIDs.Size > 1)
                    strings.Remove(documentIDs.ArrayList[1]);
            }
            
            byte[] uValue = null;
            byte[] oValue = null;
            int cryptoMode = PdfWriter.ENCRYPTION_RC4_40;
            int lengthValue = 0;  
            
            PdfObject filter = GetPdfObjectRelease(enc.Get(PdfName.FILTER));
            if (filter.Equals(PdfName.STANDARD)) {                   
                s = enc.Get(PdfName.U).ToString();
                strings.Remove(enc.Get(PdfName.U));
                uValue = DocWriter.GetISOBytes(s);
                s = enc.Get(PdfName.O).ToString();
                strings.Remove(enc.Get(PdfName.O));
                oValue = DocWriter.GetISOBytes(s);
                
                o = enc.Get(PdfName.R);
                if (!o.IsNumber()) throw new IOException("Illegal R value.");
                rValue = ((PdfNumber)o).IntValue;
                if (rValue != 2 && rValue != 3 && rValue != 4) throw new IOException("Unknown encryption type (" + rValue + ")");
                
                o = enc.Get(PdfName.P);
                if (!o.IsNumber()) throw new IOException("Illegal P value.");
                pValue = ((PdfNumber)o).IntValue;
                
                if ( rValue == 3 ){
                    o = enc.Get(PdfName.LENGTH);
                    if (!o.IsNumber())
                        throw new IOException("Illegal Length value.");
                    lengthValue = ((PdfNumber)o).IntValue;
                    if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
                        throw new IOException("Illegal Length value.");
                    cryptoMode = PdfWriter.ENCRYPTION_RC4_128;
                }
                else if (rValue == 4) {
                    lengthValue = 128;
                    PdfDictionary dic = (PdfDictionary)enc.Get(PdfName.CF);
                    if (dic == null)
                        throw new IOException("/CF not found (encryption)");
                    dic = (PdfDictionary)dic.Get(PdfName.STDCF);
                    if (dic == null)
                        throw new IOException("/StdCF not found (encryption)");
                    if (PdfName.V2.Equals(dic.Get(PdfName.CFM)))
                        cryptoMode = PdfWriter.ENCRYPTION_RC4_128;
                    else if (PdfName.AESV2.Equals(dic.Get(PdfName.CFM)))
                        cryptoMode = PdfWriter.ENCRYPTION_AES_128;
                    else
                        throw new IOException("No compatible encryption found");
                    PdfObject em = enc.Get(PdfName.ENCRYPTMETADATA);
                    if (em != null && em.ToString().Equals("false"))
                        cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
                } else {
                    cryptoMode = PdfWriter.ENCRYPTION_RC4_40;
                }
            } else if (filter.Equals(PdfName.PUBSEC)) {
                bool foundRecipient = false;
                byte[] envelopedData = null;
                PdfArray recipients = null;
                o = enc.Get(PdfName.V);
                if (!o.IsNumber()) throw new IOException("Illegal V value.");
                int vValue = ((PdfNumber)o).IntValue;
                if (vValue != 1 && vValue != 2 && vValue != 4)
                    throw new IOException("Unknown encryption type V = " + rValue);
                if ( vValue == 2 ){
                    o = enc.Get(PdfName.LENGTH);
                    if (!o.IsNumber())
                        throw new IOException("Illegal Length value.");
                    lengthValue = ((PdfNumber)o).IntValue;
                    if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
                        throw new IOException("Illegal Length value.");
                    cryptoMode = PdfWriter.ENCRYPTION_RC4_128;                
                    recipients = (PdfArray)enc.Get(PdfName.RECIPIENTS);                                
                } else if (vValue == 4) {
                    PdfDictionary dic = (PdfDictionary)enc.Get(PdfName.CF);
                    if (dic == null)
                        throw new IOException("/CF not found (encryption)");
                    dic = (PdfDictionary)dic.Get(PdfName.DEFAULTCRYPTFILER);
                    if (dic == null)
                        throw new IOException("/DefaultCryptFilter not found (encryption)");
                    if (PdfName.V2.Equals(dic.Get(PdfName.CFM)))
                    {
                        cryptoMode = PdfWriter.ENCRYPTION_RC4_128;
                        lengthValue = 128;
                    }
                    else if (PdfName.AESV2.Equals(dic.Get(PdfName.CFM)))
                    {
                        cryptoMode = PdfWriter.ENCRYPTION_AES_128;
                        lengthValue = 128;
                    }
                    else
                        throw new IOException("No compatible encryption found");
                    PdfObject em = dic.Get(PdfName.ENCRYPTMETADATA);
                    if (em != null && em.ToString().Equals("false"))
                        cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
                    
                    recipients = (PdfArray)dic.Get(PdfName.RECIPIENTS);                                    
                } else {
                    cryptoMode = PdfWriter.ENCRYPTION_RC4_40;
                    lengthValue = 40;                
                    recipients = (PdfArray)enc.Get(PdfName.RECIPIENTS);                
                }
                for (int i = 0; i<recipients.Size; i++)
                {
                    PdfObject recipient = (PdfObject)recipients.ArrayList[i];
                    strings.Remove(recipient);
                    
                    CmsEnvelopedData data = null;
                    data = new CmsEnvelopedData(recipient.GetBytes());
                
                    foreach (RecipientInformation recipientInfo in data.GetRecipientInfos().GetRecipients()) {
                        if (recipientInfo.RecipientID.Match(certificate) && !foundRecipient) {
    
                        envelopedData = recipientInfo.GetContent(certificateKey);
                        foundRecipient = true;                         
                        }
                    }                        
                }
                
                if(!foundRecipient || envelopedData == null)
                {
                    throw new IOException("Bad certificate and key.");
                }            
                SHA1 sh = new SHA1CryptoServiceProvider();
                sh.TransformBlock(envelopedData, 0, 20, envelopedData, 0);
                for (int i=0; i<recipients.Size; i++)
                {
                byte[] encodedRecipient = ((PdfObject)recipients.ArrayList[i]).GetBytes();  
                sh.TransformBlock(encodedRecipient, 0, encodedRecipient.Length, encodedRecipient, 0);
                }
                if ((cryptoMode & PdfWriter.DO_NOT_ENCRYPT_METADATA) != 0)
                    sh.TransformBlock(PdfEncryption.metadataPad, 0, PdfEncryption.metadataPad.Length, PdfEncryption.metadataPad, 0);
                sh.TransformFinalBlock(envelopedData, 0, 0);        
                encryptionKey = sh.Hash;
            }
            decrypt = new PdfEncryption();
            decrypt.SetCryptoMode(cryptoMode, lengthValue);
            
            if (filter.Equals(PdfName.STANDARD))
            {
                //check by user password
                decrypt.SetupByUserPassword(documentID, password, oValue, pValue);
                if (!EqualsArray(uValue, decrypt.userKey, (rValue == 3 || rValue == 4) ? 16 : 32)) {
                    //check by owner password
                    decrypt.SetupByOwnerPassword(documentID, password, uValue, oValue, pValue);
                    if (!EqualsArray(uValue, decrypt.userKey, (rValue == 3 || rValue == 4) ? 16 : 32)) {
                        throw new IOException("Bad user password");
                    }
                }
            } else if (filter.Equals(PdfName.PUBSEC)) {   
                decrypt.SetupByEncryptionKey(encryptionKey, lengthValue);  
            }
            for (int k = 0; k < strings.Count; ++k) {
                PdfString str = (PdfString)strings[k];
                str.Decrypt(this);
            }
            if (encDic.IsIndirect()) {
                cryptoRef = (PRIndirectReference)encDic;
                xrefObj[cryptoRef.Number] = null;
            }
        }
        
        /**
        * @param obj
        * @return a PdfObject
        */
        public static PdfObject GetPdfObjectRelease(PdfObject obj) {
            PdfObject obj2 = GetPdfObject(obj);
            ReleaseLastXrefPartial(obj);
            return obj2;
        }
        
        /**
        * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
        * if needed.
        * @param obj the <CODE>PdfObject</CODE> to read
        * @return the resolved <CODE>PdfObject</CODE>
        */    
        public static PdfObject GetPdfObject(PdfObject obj) {
            if (obj == null)
                return null;
            if (!obj.IsIndirect())
                return obj;
            PRIndirectReference refi = (PRIndirectReference)obj;
            int idx = refi.Number;
            bool appendable = refi.Reader.appendable;
            obj = refi.Reader.GetPdfObject(idx);
            if (obj == null) {
                return null;
            }
            else {
                if (appendable) {
                    switch (obj.Type) {
                        case PdfObject.NULL:
                            obj = new PdfNull();
                            break;
                        case PdfObject.BOOLEAN:
                            obj = new PdfBoolean(((PdfBoolean)obj).BooleanValue);
                            break;
                        case PdfObject.NAME:
                            obj = new PdfName(obj.GetBytes());
                            break;
                    }
                    obj.IndRef = refi;
                }
                return obj;
            }
        }
        
        /**
        * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
        * if needed. If the reader was opened in partial mode the object will be released
        * to save memory.
        * @param obj the <CODE>PdfObject</CODE> to read
        * @param parent
        * @return a PdfObject
        */    
        public static PdfObject GetPdfObjectRelease(PdfObject obj, PdfObject parent) {
            PdfObject obj2 = GetPdfObject(obj, parent);
            ReleaseLastXrefPartial(obj);
            return obj2;
        }
        
        /**
        * @param obj
        * @param parent
        * @return a PdfObject
        */
        public static PdfObject GetPdfObject(PdfObject obj, PdfObject parent) {
            if (obj == null)
                return null;
            if (!obj.IsIndirect()) {
                PRIndirectReference refi = null;
                if (parent != null && (refi = parent.IndRef) != null && refi.Reader.Appendable) {
                    switch (obj.Type) {
                        case PdfObject.NULL:
                            obj = new PdfNull();
                            break;
                        case PdfObject.BOOLEAN:
                            obj = new PdfBoolean(((PdfBoolean)obj).BooleanValue);
                            break;
                        case PdfObject.NAME:
                            obj = new PdfName(obj.GetBytes());
                            break;
                    }
                    obj.IndRef = refi;
                }
                return obj;
            }
            return GetPdfObject(obj);
        }
        
        /**
        * @param idx
        * @return a PdfObject
        */
        public PdfObject GetPdfObjectRelease(int idx) {
            PdfObject obj = GetPdfObject(idx);
            ReleaseLastXrefPartial();
            return obj;
        }
        
        /**
        * @param idx
        * @return aPdfObject
        */
        public PdfObject GetPdfObject(int idx) {
            lastXrefPartial = -1;
            if (idx < 0 || idx >= xrefObj.Count)
                return null;
            PdfObject obj = (PdfObject)xrefObj[idx];
            if (!partial || obj != null)
                return obj;
            if (idx * 2 >= xref.Length)
                return null;
            obj = ReadSingleObject(idx);
            lastXrefPartial = -1;
            if (obj != null)
                lastXrefPartial = idx;
            return obj;
        }
        /**
        * 
        */
        public void ResetLastXrefPartial() {
            lastXrefPartial = -1;
        }
        
        /**
        * 
        */
        public void ReleaseLastXrefPartial() {
            if (partial && lastXrefPartial != -1) {
                xrefObj[lastXrefPartial] = null;
                lastXrefPartial = -1;
            }
        }
        /**
        * @param obj
        */
        public static void ReleaseLastXrefPartial(PdfObject obj) {
            if (obj == null)
                return;
            if (!obj.IsIndirect())
                return;
            PRIndirectReference refi = (PRIndirectReference)obj;
            PdfReader reader = refi.Reader;
            if (reader.partial && reader.lastXrefPartial != -1 && reader.lastXrefPartial == refi.Number) {
                reader.xrefObj[reader.lastXrefPartial] = null;
            }
            reader.lastXrefPartial = -1;
        }
        private void SetXrefPartialObject(int idx, PdfObject obj) {
            if (!partial || idx < 0)
                return;
            xrefObj[idx] = obj;
        }
        
        /**
        * @param obj
        * @return an indirect reference
        */
        public PRIndirectReference AddPdfObject(PdfObject obj) {
            xrefObj.Add(obj);
            return new PRIndirectReference(this, xrefObj.Count - 1);
        }
        
        protected internal void ReadPages() {
            pageInh = new ArrayList();
            catalog = (PdfDictionary)GetPdfObject(trailer.Get(PdfName.ROOT));
            rootPages = (PdfDictionary)GetPdfObject(catalog.Get(PdfName.PAGES));
            pageRefs = new PageRefs(this);
        }
        
        protected internal void ReadDocObjPartial() {
            xrefObj = 
// MASC 20070308. CF compatibility patch
#if !NETCF
				ArrayList.Repeat(
#else
				ArrayListEx.Repeat(
#endif
					null, xref.Length / 2
				);
            ReadDecryptedDocObj();
            if (objStmToOffset != null) {
                int[] keys = objStmToOffset.GetKeys();
                for (int k = 0; k < keys.Length; ++k) {
                    int n = keys[k];
                    objStmToOffset[n] = xref[n * 2];
                    xref[n * 2] = -1;
                }
            }
        }
        protected internal PdfObject ReadSingleObject(int k) {
            strings.Clear();
            int k2 = k * 2;
            int pos = xref[k2];
            if (pos < 0)
                return null;
            if (xref[k2 + 1] > 0)
                pos = objStmToOffset[xref[k2 + 1]];
            if (pos == 0)
                return null;
            tokens.Seek(pos);
            tokens.NextValidToken();
            if (tokens.TokenType != PRTokeniser.TK_NUMBER)
                tokens.ThrowError("Invalid object number.");
            objNum = tokens.IntValue;
            tokens.NextValidToken();
            if (tokens.TokenType != PRTokeniser.TK_NUMBER)
                tokens.ThrowError("Invalid generation number.");
            objGen = tokens.IntValue;
            tokens.NextValidToken();
            if (!tokens.StringValue.Equals("obj"))
                tokens.ThrowError("Token 'obj' expected.");
            PdfObject obj;
            try {
                obj = ReadPRObject();
                for (int j = 0; j < strings.Count; ++j) {
                    PdfString str = (PdfString)strings[j];
                    str.Decrypt(this);
                }
                if (obj.IsStream()) {
                    CheckPRStreamLength((PRStream)obj);
                }
            }
            catch {
                obj = null;
            }
            if (xref[k2 + 1] > 0) {
                obj = ReadOneObjStm((PRStream)obj, xref[k2]);
            }
            xrefObj[k] = obj;
            return obj;
        }
        
        protected internal PdfObject ReadOneObjStm(PRStream stream, int idx) {
            int first = ((PdfNumber)GetPdfObject(stream.Get(PdfName.FIRST))).IntValue;
            byte[] b = GetStreamBytes(stream, tokens.File);
            PRTokeniser saveTokens = tokens;
            tokens = new PRTokeniser(b);
            try {
                int address = 0;
                bool ok = true;
                ++idx;
                for (int k = 0; k < idx; ++k) {
                    ok = tokens.NextToken();
                    if (!ok)
                        break;
                    if (tokens.TokenType != PRTokeniser.TK_NUMBER) {
                        ok = false;
                        break;
                    }
                    ok = tokens.NextToken();
                    if (!ok)
                        break;
                    if (tokens.TokenType != PRTokeniser.TK_NUMBER) {
                        ok = false;
                        break;
                    }
                    address = tokens.IntValue + first;
                }
                if (!ok)
                    throw new IOException("Error reading ObjStm");
                tokens.Seek(address);
                return ReadPRObject();
            }
            finally {
                tokens = saveTokens;
            }
        }
        /**
        * @return the percentage of the cross reference table that has been read
        */
        public double DumpPerc() {
            int total = 0;
            for (int k = 0; k < xrefObj.Count; ++k) {
                if (xrefObj[k] != null)
                    ++total;
            }
            return (total * 100.0 / xrefObj.Count);
        }
        
        protected internal void ReadDocObj() {
            ArrayList streams = new ArrayList();
            xrefObj = 
// MASC 20070308. CF compatibility patch
#if !NETCF
				ArrayList.Repeat(
#else
				ArrayListEx.Repeat(
#endif
					null, xref.Length / 2
				);
            for (int k = 2; k < xref.Length; k += 2) {
                int pos = xref[k];
                if (pos <= 0 || xref[k + 1] > 0)
                    continue;
                tokens.Seek(pos);
                tokens.NextValidToken();
                if (tokens.TokenType != PRTokeniser.TK_NUMBER)
                    tokens.ThrowError("Invalid object number.");
                objNum = tokens.IntValue;
                tokens.NextValidToken();
                if (tokens.TokenType != PRTokeniser.TK_NUMBER)
                    tokens.ThrowError("Invalid generation number.");
                objGen = tokens.IntValue;
                tokens.NextValidToken();
                if (!tokens.StringValue.Equals("obj"))
                    tokens.ThrowError("Token 'obj' expected.");
                PdfObject obj;
                try {
                    obj = ReadPRObject();
                    if (obj.IsStream()) {
                        streams.Add(obj);
                    }
                }
                catch {
                    obj = null;
                }
                xrefObj[k / 2] = obj;
            }
            for (int k = 0; k < streams.Count; ++k) {
                CheckPRStreamLength((PRStream)streams[k]);
            }
            ReadDecryptedDocObj();
            if (objStmMark != null) {
                foreach (DictionaryEntry entry  in objStmMark) {
                    int n = (int)entry.Key;
                    IntHashtable h = (IntHashtable)entry.Value;
                    ReadObjStm((PRStream)xrefObj[n], h);
                    xrefObj[n] = null;
                }
                objStmMark = null;
            }
            xref = null;
        }
        
        private void CheckPRStreamLength(PRStream stream) {
            int fileLength = tokens.Length;
            int start = stream.Offset;
            bool calc = false;
            int streamLength = 0;
            PdfObject obj = GetPdfObjectRelease(stream.Get(PdfName.LENGTH));
            if (obj != null && obj.Type == PdfObject.NUMBER) {
                streamLength = ((PdfNumber)obj).IntValue;
                if (streamLength + start > fileLength - 20)
                    calc = true;
                else {
                    tokens.Seek(start + streamLength);
                    String line = tokens.ReadString(20);
                    if (!line.StartsWith("\nendstream") &&
                    !line.StartsWith("\r\nendstream") &&
                    !line.StartsWith("\rendstream") &&
                    !line.StartsWith("endstream"))
                        calc = true;
                }
            }
            else
                calc = true;
            if (calc) {
                byte[] tline = new byte[16];
                tokens.Seek(start);
                while (true) {
                    int pos = tokens.FilePointer;
                    if (!tokens.ReadLineSegment(tline))
                        break;
                    if (Equalsn(tline, endstream)) {
                        streamLength = pos - start;
                        break;
                    }
                    if (Equalsn(tline, endobj)) {
                        tokens.Seek(pos - 16);
                        String s = tokens.ReadString(16);
                        int index = s.IndexOf("endstream");
                        if (index >= 0)
                            pos = pos - 16 + index;
                        streamLength = pos - start;
                        break;
                    }
                }
            }
            stream.Length = streamLength;
        }
        
        protected internal void ReadObjStm(PRStream stream, IntHashtable map) {
            int first = ((PdfNumber)GetPdfObject(stream.Get(PdfName.FIRST))).IntValue;
            int n = ((PdfNumber)GetPdfObject(stream.Get(PdfName.N))).IntValue;
            byte[] b = GetStreamBytes(stream, tokens.File);
            PRTokeniser saveTokens = tokens;
            tokens = new PRTokeniser(b);
            try {
                int[] address = new int[n];
                int[] objNumber = new int[n];
                bool ok = true;
                for (int k = 0; k < n; ++k) {
                    ok = tokens.NextToken();
                    if (!ok)
                        break;
                    if (tokens.TokenType != PRTokeniser.TK_NUMBER) {
                        ok = false;
                        break;
                    }
                    objNumber[k] = tokens.IntValue;
                    ok = tokens.NextToken();
                    if (!ok)
                        break;
                    if (tokens.TokenType != PRTokeniser.TK_NUMBER) {
                        ok = false;
                        break;
                    }
                    address[k] = tokens.IntValue + first;
                }
                if (!ok)
                    throw new IOException("Error reading ObjStm");
                for (int k = 0; k < n; ++k) {
                    if (map.ContainsKey(k)) {
                        tokens.Seek(address[k]);
                        PdfObject obj = ReadPRObject();
                        xrefObj[objNumber[k]] = obj;
                    }
                }            
            }
            finally {
                tokens = saveTokens;
            }
        }
        
        /**
        * Eliminates the reference to the object freeing the memory used by it and clearing
        * the xref entry.
        * @param obj the object. If it's an indirect reference it will be eliminated
        * @return the object or the already erased dereferenced object
        */    
        public static PdfObject KillIndirect(PdfObject obj) {
            if (obj == null || obj.IsNull())
                return null;
            PdfObject ret = GetPdfObjectRelease(obj);
            if (obj.IsIndirect()) {
                PRIndirectReference refi = (PRIndirectReference)obj;
                PdfReader reader = refi.Reader;
                int n = refi.Number;
                reader.xrefObj[n] = null;
                if (reader.partial)
                    reader.xref[n * 2] = -1;
            }
            return ret;
        }
        
        private void EnsureXrefSize(int size) {
            if (size == 0)
                return;
            if (xref == null)
                xref = new int[size];
            else {
                if (xref.Length < size) {
                    int[] xref2 = new int[size];
                    Array.Copy(xref, 0, xref2, 0, xref.Length);
                    xref = xref2;
                }
            }
        }
        
        protected internal void ReadXref() {
            hybridXref = false;
            newXrefType = false;
            tokens.Seek(tokens.Startxref);
            tokens.NextToken();
            if (!tokens.StringValue.Equals("startxref"))
                throw new IOException("startxref not found.");
            tokens.NextToken();
            if (tokens.TokenType != PRTokeniser.TK_NUMBER)
                throw new IOException("startxref is not followed by a number.");
            int startxref = tokens.IntValue;
            lastXref = startxref;
            eofPos = tokens.FilePointer;
            try {
                if (ReadXRefStream(startxref)) {
                    newXrefType = true;
                    return;
                }
            }
            catch {}
            xref = null;
            tokens.Seek(startxref);
            trailer = ReadXrefSection();
            PdfDictionary trailer2 = trailer;
            while (true) {
                PdfNumber prev = (PdfNumber)trailer2.Get(PdfName.PREV);
                if (prev == null)
                    break;
                tokens.Seek(prev.IntValue);
                trailer2 = ReadXrefSection();
            }
        }
        
        protected internal PdfDictionary ReadXrefSection() {
            tokens.NextValidToken();
            if (!tokens.StringValue.Equals("xref"))
                tokens.ThrowError("xref subsection not found");
            int start = 0;
            int end = 0;
            int pos = 0;
            int gen = 0;
            while (true) {
                tokens.NextValidToken();
                if (tokens.StringValue.Equals("trailer"))
                    break;
                if (tokens.TokenType != PRTokeniser.TK_NUMBER)
                    tokens.ThrowError("Object number of the first object in this xref subsection not found");
                start = tokens.IntValue;
                tokens.NextValidToken();
                if (tokens.TokenType != PRTokeniser.TK_NUMBER)
                    tokens.ThrowError("Number of entries in this xref subsection not found");
                end = tokens.IntValue + start;
                if (start == 1) { // fix incorrect start number
                    int back = tokens.FilePointer;
                    tokens.NextValidToken();
                    pos = tokens.IntValue;
                    tokens.NextValidToken();
                    gen = tokens.IntValue;
                    if (pos == 0 && gen == 65535) {
                        --start;
                        --end;
                    }
                    tokens.Seek(back);
                }
                EnsureXrefSize(end * 2);
                for (int k = start; k < end; ++k) {
                    tokens.NextValidToken();
                    pos = tokens.IntValue;
                    tokens.NextValidToken();
                    gen = tokens.IntValue;
                    tokens.NextValidToken();
                    int p = k * 2;
                    if (tokens.StringValue.Equals("n")) {
                        if (xref[p] == 0 && xref[p + 1] == 0) {
    //                        if (pos == 0)
    //                            tokens.ThrowError("File position 0 cross-reference entry in this xref subsection");
                            xref[p] = pos;
                        }
                    }
                    else if (tokens.StringValue.Equals("f")) {
                        if (xref[p] == 0 && xref[p + 1] == 0)
                            xref[p] = -1;
                    }
                    else
                        tokens.ThrowError("Invalid cross-reference entry in this xref subsection");
                }
            }
            PdfDictionary trailer = (PdfDictionary)ReadPRObject();
            PdfNumber xrefSize = (PdfNumber)trailer.Get(PdfName.SIZE);
            EnsureXrefSize(xrefSize.IntValue * 2);
            PdfObject xrs = trailer.Get(PdfName.XREFSTM);
            if (xrs != null && xrs.IsNumber()) {
                int loc = ((PdfNumber)xrs).IntValue;
                try {
                    ReadXRefStream(loc);
                    newXrefType = true;
                    hybridXref = true;
                }
                catch (IOException e) {
                    xref = null;
                    throw e;
                }
            }
            return trailer;
        }
        
        protected internal bool ReadXRefStream(int ptr) {
            tokens.Seek(ptr);
            int thisStream = 0;
            if (!tokens.NextToken())
                return false;
            if (tokens.TokenType != PRTokeniser.TK_NUMBER)
                return false;
            thisStream = tokens.IntValue;
            if (!tokens.NextToken() || tokens.TokenType != PRTokeniser.TK_NUMBER)
                return false;
            if (!tokens.NextToken() || !tokens.StringValue.Equals("obj"))
                return false;
            PdfObject objecto = ReadPRObject();
            PRStream stm = null;
            if (objecto.IsStream()) {
                stm = (PRStream)objecto;
                if (!PdfName.XREF.Equals(stm.Get(PdfName.TYPE)))
                    return false;
            }
            else
                return false;
            if (trailer == null) {
                trailer = new PdfDictionary();
                trailer.Merge(stm);
            }
            stm.Length = ((PdfNumber)stm.Get(PdfName.LENGTH)).IntValue;
            int size = ((PdfNumber)stm.Get(PdfName.SIZE)).IntValue;
            PdfArray index;
            PdfObject obj = stm.Get(PdfName.INDEX);
            if (obj == null) {
                index = new PdfArray();
                index.Add(new int[]{0, size});
            }
            else
                index = (PdfArray)obj;
            PdfArray w = (PdfArray)stm.Get(PdfName.W);
            int prev = -1;
            obj = stm.Get(PdfName.PREV);
            if (obj != null)
                prev = ((PdfNumber)obj).IntValue;
            // Each xref pair is a position
            // type 0 -> -1, 0
            // type 1 -> offset, 0
            // type 2 -> index, obj num
            EnsureXrefSize(size * 2);
            if (objStmMark == null && !partial)
                objStmMark = new Hashtable();
            if (objStmToOffset == null && partial)
                objStmToOffset = new IntHashtable();
            byte[] b = GetStreamBytes(stm, tokens.File);
            int bptr = 0;
            ArrayList wa = w.ArrayList;
            int[] wc = new int[3];
            for (int k = 0; k < 3; ++k)
                wc[k] = ((PdfNumber)wa[k]).IntValue;
            ArrayList sections = index.ArrayList;
            for (int idx = 0; idx < sections.Count; idx += 2) {
                int start = ((PdfNumber)sections[idx]).IntValue;
                int length = ((PdfNumber)sections[idx + 1]).IntValue;
                EnsureXrefSize((start + length) * 2);
                while (length-- > 0) {
                    int type = 1;
                    if (wc[0] > 0) {
                        type = 0;
                        for (int k = 0; k < wc[0]; ++k)
                            type = (type << 8) + (b[bptr++] & 0xff);
                    }
                    int field2 = 0;
                    for (int k = 0; k < wc[1]; ++k)
                        field2 = (field2 << 8) + (b[bptr++] & 0xff);
                    int field3 = 0;
                    for (int k = 0; k < wc[2]; ++k)
                        field3 = (field3 << 8) + (b[bptr++] & 0xff);
                    int baseb = start * 2;
                    if (xref[baseb] == 0 && xref[baseb + 1] == 0) {
                        switch (type) {
                            case 0:
                                xref[baseb] = -1;
                                break;
                            case 1:
                                xref[baseb] = field2;
                                break;
                            case 2:
                                xref[baseb] = field3;
                                xref[baseb + 1] = field2;
                                if (partial) {
                                    objStmToOffset[field2] = 0;
                                }
                                else {
                                    IntHashtable seq = (IntHashtable)objStmMark[field2];
                                    if (seq == null) {
                                        seq = new IntHashtable();
                                        seq[field3] = 1;
                                        objStmMark[field2] = seq;
                                    }
                                    else
                                        seq[field3] = 1;
                                }
                                break;
                        }
                    }
                    ++start;
                }
            }
            thisStream *= 2;
            if (thisStream < xref.Length)
                xref[thisStream] = -1;
                
            if (prev == -1)
                return true;
            return ReadXRefStream(prev);
        }
        
        protected internal void RebuildXref() {
            hybridXref = false;
            newXrefType = false;
            tokens.Seek(0);
            int[][] xr = new int[1024][];
            int top = 0;
            trailer = null;
            byte[] line = new byte[64];
            for (;;) {
                int pos = tokens.FilePointer;
                if (!tokens.ReadLineSegment(line))
                    break;
                if (line[0] == 't') {
                    if (!PdfEncodings.ConvertToString(line, null).StartsWith("trailer"))
                        continue;
                    tokens.Seek(pos);
                    tokens.NextToken();
                    pos = tokens.FilePointer;
                    try {
                        PdfDictionary dic = (PdfDictionary)ReadPRObject();
                        if (dic.Get(PdfName.ROOT) != null)
                            trailer = dic;
                        else
                            tokens.Seek(pos);
                    }
                    catch {
                        tokens.Seek(pos);
                    }
                }
                else if (line[0] >= '0' && line[0] <= '9') {
                    int[] obj = PRTokeniser.CheckObjectStart(line);
                    if (obj == null)
                        continue;
                    int num = obj[0];
                    int gen = obj[1];
                    if (num >= xr.Length) {
                        int newLength = num * 2;
                        int[][] xr2 = new int[newLength][];
                        Array.Copy(xr, 0, xr2, 0, top);
                        xr = xr2;
                    }
                    if (num >= top)
                        top = num + 1;
                    if (xr[num] == null || gen >= xr[num][1]) {
                        obj[0] = pos;
                        xr[num] = obj;
                    }
                }
            }
            if (trailer == null)
                throw new IOException("trailer not found.");
            xref = new int[top * 2];
            for (int k = 0; k < top; ++k) {
                int[] obj = xr[k];
                if (obj != null)
                    xref[k * 2] = obj[0];
            }
        }
        
        protected internal PdfDictionary ReadDictionary() {
            PdfDictionary dic = new PdfDictionary();
            while (true) {
                tokens.NextValidToken();
                if (tokens.TokenType == PRTokeniser.TK_END_DIC)
                    break;
                if (tokens.TokenType != PRTokeniser.TK_NAME)
                    tokens.ThrowError("Dictionary key is not a name.");
                PdfName name = new PdfName(tokens.StringValue, false);
                PdfObject obj = ReadPRObject();
                int type = obj.Type;
                if (-type == PRTokeniser.TK_END_DIC)
                    tokens.ThrowError("Unexpected '>>'");
                if (-type == PRTokeniser.TK_END_ARRAY)
                    tokens.ThrowError("Unexpected ']'");
                dic.Put(name, obj);
            }
            return dic;
        }
        
        protected internal PdfArray ReadArray() {
            PdfArray array = new PdfArray();
            while (true) {
                PdfObject obj = ReadPRObject();
                int type = obj.Type;
                if (-type == PRTokeniser.TK_END_ARRAY)
                    break;
                if (-type == PRTokeniser.TK_END_DIC)
                    tokens.ThrowError("Unexpected '>>'");
                array.Add(obj);
            }
            return array;
        }
        
        protected internal PdfObject ReadPRObject() {
            tokens.NextValidToken();
            int type = tokens.TokenType;
            switch (type) {
                case PRTokeniser.TK_START_DIC: {
                    PdfDictionary dic = ReadDictionary();
                    int pos = tokens.FilePointer;
                    // be careful in the trailer. May not be a "next" token.
                    if (tokens.NextToken() && tokens.StringValue.Equals("stream")) {
                        int ch = tokens.Read();
                        if (ch != '\n')
                            ch = tokens.Read();
                        if (ch != '\n')
                            tokens.BackOnePosition(ch);
                        PRStream stream = new PRStream(this, tokens.FilePointer);
                        stream.Merge(dic);
                        stream.ObjNum = objNum;
                        stream.ObjGen = objGen;
                        return stream;
                    }
                    else {
                        tokens.Seek(pos);
                        return dic;
                    }
                }
                case PRTokeniser.TK_START_ARRAY:
                    return ReadArray();
                case PRTokeniser.TK_NUMBER:
                    return new PdfNumber(tokens.StringValue);
                case PRTokeniser.TK_STRING:
                    PdfString str = new PdfString(tokens.StringValue, null).SetHexWriting(tokens.IsHexString());
                    str.SetObjNum(objNum, objGen);
                    if (strings != null)
                        strings.Add(str);
                    return str;
                case PRTokeniser.TK_NAME:
                    return new PdfName(tokens.StringValue, false);
                case PRTokeniser.TK_REF:
                    int num = tokens.Reference;
                    PRIndirectReference refi = new PRIndirectReference(this, num, tokens.Generation);
                    return refi;
                default:
                    String sv = tokens.StringValue;
                    if ("null".Equals(sv))
                        return PdfNull.PDFNULL;
                    else if ("true".Equals(sv))
                        return PdfBoolean.PDFTRUE;
                    else if ("false".Equals(sv))
                        return PdfBoolean.PDFFALSE;
                    return new PdfLiteral(-type, tokens.StringValue);
            }
        }
        
        /** Decodes a stream that has the FlateDecode filter.
        * @param in the input data
        * @return the decoded data
        */    
        public static byte[] FlateDecode(byte[] inp) {
            byte[] b = FlateDecode(inp, true);
            if (b == null)
                return FlateDecode(inp, false);
            return b;
        }
        
        /**
        * @param in
        * @param dicPar
        * @return a byte array
        */
        public static byte[] DecodePredictor(byte[] inp, PdfObject dicPar) {
            if (dicPar == null || !dicPar.IsDictionary())
                return inp;
            PdfDictionary dic = (PdfDictionary)dicPar;
            PdfObject obj = GetPdfObject(dic.Get(PdfName.PREDICTOR));
            if (obj == null || !obj.IsNumber())
                return inp;
            int predictor = ((PdfNumber)obj).IntValue;
            if (predictor < 10)
                return inp;
            int width = 1;
            obj = GetPdfObject(dic.Get(PdfName.COLUMNS));
            if (obj != null && obj.IsNumber())
                width = ((PdfNumber)obj).IntValue;
            int colors = 1;
            obj = GetPdfObject(dic.Get(PdfName.COLORS));
            if (obj != null && obj.IsNumber())
                colors = ((PdfNumber)obj).IntValue;
            int bpc = 8;
            obj = GetPdfObject(dic.Get(PdfName.BITSPERCOMPONENT));
            if (obj != null && obj.IsNumber())
                bpc = ((PdfNumber)obj).IntValue;
            MemoryStream dataStream = new MemoryStream(inp);
            MemoryStream fout = new MemoryStream(inp.Length);
            int bytesPerPixel = colors * bpc / 8;
            int bytesPerRow = (colors*width*bpc + 7)/8;
            byte[] curr = new byte[bytesPerRow];
            byte[] prior = new byte[bytesPerRow];
            
            // Decode the (sub)image row-by-row
            while (true) {
                // Read the filter type byte and a row of data
                int filter = 0;
                try {
                    filter = dataStream.ReadByte();
                    if (filter < 0) {
                        return fout.ToArray();
                    }
                    int tot = 0;
                    while (tot < bytesPerRow) {
                        int n = dataStream.Read(curr, tot, bytesPerRow - tot);
                        if (n <= 0)
                            return fout.ToArray();
                        tot += n;
                    }
                } catch {
                    return fout.ToArray();
                }
                
                switch (filter) {
                    case 0: //PNG_FILTER_NONE
                        break;
                    case 1: //PNG_FILTER_SUB
                        for (int i = bytesPerPixel; i < bytesPerRow; i++) {
                            curr[i] += curr[i - bytesPerPixel];
                        }
                        break;
                    case 2: //PNG_FILTER_UP
                        for (int i = 0; i < bytesPerRow; i++) {
                            curr[i] += prior[i];
                        }
                        break;
                    case 3: //PNG_FILTER_AVERAGE
                        for (int i = 0; i < bytesPerPixel; i++) {
                            curr[i] += (byte)(prior[i] / 2);
                        }
                        for (int i = bytesPerPixel; i < bytesPerRow; i++) {
                            curr[i] += (byte)(((curr[i - bytesPerPixel] & 0xff) + (prior[i] & 0xff))/2);
                        }
                        break;
                    case 4: //PNG_FILTER_PAETH
                        for (int i = 0; i < bytesPerPixel; i++) {
                            curr[i] += prior[i];
                        }
                        for (int i = bytesPerPixel; i < bytesPerRow; i++) {
                            int a = curr[i - bytesPerPixel] & 0xff;
                            int b = prior[i] & 0xff;
                            int c = prior[i - bytesPerPixel] & 0xff;
                            int p = a + b - c;
                            int pa = Math.Abs(p - a);
                            int pb = Math.Abs(p - b);
                            int pc = Math.Abs(p - c);
                            int ret;
                            if ((pa <= pb) && (pa <= pc)) {
                                ret = a;
                            } else if (pb <= pc) {
                                ret = b;
                            } else {
                                ret = c;
                            }
                            curr[i] += (byte)(ret);
                        }
                        break;
                    default:
                        // Error -- uknown filter type
                        throw new Exception("PNG filter unknown.");
                }


Answer:                 fout.Write(curr, 0, curr.Length);