0001: /*
0002: * $Id: PdfReader.java 2911 2007-09-06 06:39:00Z xlv $
0003: * $Name$
0004: *
0005: * Copyright 2001, 2002 Paulo Soares
0006: *
0007: * The contents of this file are subject to the Mozilla Public License Version 1.1
0008: * (the "License"); you may not use this file except in compliance with the License.
0009: * You may obtain a copy of the License at http://www.mozilla.org/MPL/
0010: *
0011: * Software distributed under the License is distributed on an "AS IS" basis,
0012: * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
0013: * for the specific language governing rights and limitations under the License.
0014: *
0015: * The Original Code is 'iText, a free JAVA-PDF library'.
0016: *
0017: * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
0018: * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
0019: * All Rights Reserved.
0020: * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
0021: * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
0022: *
0023: * Contributor(s): all the names of the contributors are added in the source code
0024: * where applicable.
0025: *
0026: * Alternatively, the contents of this file may be used under the terms of the
0027: * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
0028: * provisions of LGPL are applicable instead of those above. If you wish to
0029: * allow use of your version of this file only under the terms of the LGPL
0030: * License and not to allow others to use your version of this file under
0031: * the MPL, indicate your decision by deleting the provisions above and
0032: * replace them with the notice and other provisions required by the LGPL.
0033: * If you do not delete the provisions above, a recipient may use your version
0034: * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
0035: *
0036: * This library is free software; you can redistribute it and/or modify it
0037: * under the terms of the MPL as stated above or under the terms of the GNU
0038: * Library General Public License as published by the Free Software Foundation;
0039: * either version 2 of the License, or any later version.
0040: *
0041: * This library is distributed in the hope that it will be useful, but WITHOUT
0042: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
0043: * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
0044: * details.
0045: *
0046: * If you didn't download this code from the following link, you should check if
0047: * you aren't using an obsolete version:
0048: * http://www.lowagie.com/iText/
0049: */
0050:
0051: package com.lowagie.text.pdf;
0052:
0053: import java.io.ByteArrayInputStream;
0054: import java.io.ByteArrayOutputStream;
0055: import java.io.DataInputStream;
0056: import java.io.IOException;
0057: import java.io.InputStream;
0058: import java.net.URL;
0059: import java.util.ArrayList;
0060: import java.util.Arrays;
0061: import java.util.Collections;
0062: import java.util.HashMap;
0063: import java.util.Iterator;
0064: import java.util.List;
0065: import java.util.Map;
0066: import java.util.Set;
0067: import java.util.zip.InflaterInputStream;
0068: import java.util.Stack;
0069: import java.security.Key;
0070: import java.security.MessageDigest;
0071: import java.security.cert.Certificate;
0072:
0073: import com.lowagie.text.ExceptionConverter;
0074: import com.lowagie.text.PageSize;
0075: import com.lowagie.text.Rectangle;
0076: import com.lowagie.text.pdf.interfaces.PdfViewerPreferences;
0077: import com.lowagie.text.pdf.internal.PdfViewerPreferencesImp;
0078:
0079: import org.bouncycastle.cms.CMSEnvelopedData;
0080: import org.bouncycastle.cms.RecipientInformation;
0081:
0082: /** Reads a PDF document.
0083: * @author Paulo Soares (psoares@consiste.pt)
0084: * @author Kazuya Ujihara
0085: */
0086: public class PdfReader implements PdfViewerPreferences {
0087:
0088: static final PdfName pageInhCandidates[] = { PdfName.MEDIABOX,
0089: PdfName.ROTATE, PdfName.RESOURCES, PdfName.CROPBOX };
0090:
0091: static final byte endstream[] = PdfEncodings.convertToBytes(
0092: "endstream", null);
0093: static final byte endobj[] = PdfEncodings.convertToBytes("endobj",
0094: null);
0095: protected PRTokeniser tokens;
0096: // Each xref pair is a position
0097: // type 0 -> -1, 0
0098: // type 1 -> offset, 0
0099: // type 2 -> index, obj num
0100: protected int xref[];
0101: protected HashMap objStmMark;
0102: protected IntHashtable objStmToOffset;
0103: protected boolean newXrefType;
0104: private ArrayList xrefObj;
0105: PdfDictionary rootPages;
0106: protected PdfDictionary trailer;
0107: protected PdfDictionary catalog;
0108: protected PageRefs pageRefs;
0109: protected PRAcroForm acroForm = null;
0110: protected boolean acroFormParsed = false;
0111: protected boolean encrypted = false;
0112: protected boolean rebuilt = false;
0113: protected int freeXref;
0114: protected boolean tampered = false;
0115: protected int lastXref;
0116: protected int eofPos;
0117: protected char pdfVersion;
0118: protected PdfEncryption decrypt;
0119: protected byte password[] = null; //added by ujihara for decryption
0120: protected Key certificateKey = null; //added by Aiken Sam for certificate decryption
0121: protected Certificate certificate = null; //added by Aiken Sam for certificate decryption
0122: protected String certificateKeyProvider = null; //added by Aiken Sam for certificate decryption
0123: private boolean ownerPasswordUsed;
0124: protected ArrayList strings = new ArrayList();
0125: protected boolean sharedStreams = true;
0126: protected boolean consolidateNamedDestinations = false;
0127: protected int rValue;
0128: protected int pValue;
0129: private int objNum;
0130: private int objGen;
0131: private int fileLength;
0132: private boolean hybridXref;
0133: private int lastXrefPartial = -1;
0134: private boolean partial;
0135: private PRIndirectReference cryptoRef;
0136: private PdfViewerPreferencesImp viewerPreferences = new PdfViewerPreferencesImp();
0137:
0138: /**
0139: * Holds value of property appendable.
0140: */
0141: private boolean appendable;
0142:
0143: protected PdfReader() {
0144: }
0145:
0146: /** Reads and parses a PDF document.
0147: * @param filename the file name of the document
0148: * @throws IOException on error
0149: */
0150: public PdfReader(String filename) throws IOException {
0151: this (filename, null);
0152: }
0153:
0154: /** Reads and parses a PDF document.
0155: * @param filename the file name of the document
0156: * @param ownerPassword the password to read the document
0157: * @throws IOException on error
0158: */
0159: public PdfReader(String filename, byte ownerPassword[])
0160: throws IOException {
0161: password = ownerPassword;
0162: tokens = new PRTokeniser(filename);
0163: readPdf();
0164: }
0165:
0166: /** Reads and parses a PDF document.
0167: * @param pdfIn the byte array with the document
0168: * @throws IOException on error
0169: */
0170: public PdfReader(byte pdfIn[]) throws IOException {
0171: this (pdfIn, null);
0172: }
0173:
0174: /** Reads and parses a PDF document.
0175: * @param pdfIn the byte array with the document
0176: * @param ownerPassword the password to read the document
0177: * @throws IOException on error
0178: */
0179: public PdfReader(byte pdfIn[], byte ownerPassword[])
0180: throws IOException {
0181: password = ownerPassword;
0182: tokens = new PRTokeniser(pdfIn);
0183: readPdf();
0184: }
0185:
0186: /** Reads and parses a PDF document.
0187: * @param filename the file name of the document
0188: * @param certificate the certificate to read the document
0189: * @param certificateKey the private key of the certificate
0190: * @param certificateKeyProvider the security provider for certificateKey
0191: * @throws IOException on error
0192: */
0193: public PdfReader(String filename, Certificate certificate,
0194: Key certificateKey, String certificateKeyProvider)
0195: throws IOException {
0196: this .certificate = certificate;
0197: this .certificateKey = certificateKey;
0198: this .certificateKeyProvider = certificateKeyProvider;
0199: tokens = new PRTokeniser(filename);
0200: readPdf();
0201: }
0202:
0203: /** Reads and parses a PDF document.
0204: * @param url the URL of the document
0205: * @throws IOException on error
0206: */
0207: public PdfReader(URL url) throws IOException {
0208: this (url, null);
0209: }
0210:
0211: /** Reads and parses a PDF document.
0212: * @param url the URL of the document
0213: * @param ownerPassword the password to read the document
0214: * @throws IOException on error
0215: */
0216: public PdfReader(URL url, byte ownerPassword[]) throws IOException {
0217: password = ownerPassword;
0218: tokens = new PRTokeniser(new RandomAccessFileOrArray(url));
0219: readPdf();
0220: }
0221:
0222: /**
0223: * Reads and parses a PDF document.
0224: * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
0225: * end but is not closed
0226: * @param ownerPassword the password to read the document
0227: * @throws IOException on error
0228: */
0229: public PdfReader(InputStream is, byte ownerPassword[])
0230: throws IOException {
0231: password = ownerPassword;
0232: tokens = new PRTokeniser(new RandomAccessFileOrArray(is));
0233: readPdf();
0234: }
0235:
0236: /**
0237: * Reads and parses a PDF document.
0238: * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
0239: * end but is not closed
0240: * @throws IOException on error
0241: */
0242: public PdfReader(InputStream is) throws IOException {
0243: this (is, null);
0244: }
0245:
0246: /**
0247: * Reads and parses a pdf document. Contrary to the other constructors only the xref is read
0248: * into memory. The reader is said to be working in "partial" mode as only parts of the pdf
0249: * are read as needed. The pdf is left open but may be closed at any time with
0250: * <CODE>PdfReader.close()</CODE>, reopen is automatic.
0251: * @param raf the document location
0252: * @param ownerPassword the password or <CODE>null</CODE> for no password
0253: * @throws IOException on error
0254: */
0255: public PdfReader(RandomAccessFileOrArray raf, byte ownerPassword[])
0256: throws IOException {
0257: password = ownerPassword;
0258: partial = true;
0259: tokens = new PRTokeniser(raf);
0260: readPdfPartial();
0261: }
0262:
0263: /** Creates an independent duplicate.
0264: * @param reader the <CODE>PdfReader</CODE> to duplicate
0265: */
0266: public PdfReader(PdfReader reader) {
0267: this .appendable = reader.appendable;
0268: this .consolidateNamedDestinations = reader.consolidateNamedDestinations;
0269: this .encrypted = reader.encrypted;
0270: this .rebuilt = reader.rebuilt;
0271: this .sharedStreams = reader.sharedStreams;
0272: this .tampered = reader.tampered;
0273: this .password = reader.password;
0274: this .pdfVersion = reader.pdfVersion;
0275: this .eofPos = reader.eofPos;
0276: this .freeXref = reader.freeXref;
0277: this .lastXref = reader.lastXref;
0278: this .tokens = new PRTokeniser(reader.tokens.getSafeFile());
0279: if (reader.decrypt != null)
0280: this .decrypt = new PdfEncryption(reader.decrypt);
0281: this .pValue = reader.pValue;
0282: this .rValue = reader.rValue;
0283: this .xrefObj = new ArrayList(reader.xrefObj);
0284: for (int k = 0; k < reader.xrefObj.size(); ++k) {
0285: this .xrefObj.set(k, duplicatePdfObject(
0286: (PdfObject) reader.xrefObj.get(k), this ));
0287: }
0288: this .pageRefs = new PageRefs(reader.pageRefs, this );
0289: this .trailer = (PdfDictionary) duplicatePdfObject(
0290: reader.trailer, this );
0291: this .catalog = (PdfDictionary) getPdfObject(trailer
0292: .get(PdfName.ROOT));
0293: this .rootPages = (PdfDictionary) getPdfObject(catalog
0294: .get(PdfName.PAGES));
0295: this .fileLength = reader.fileLength;
0296: this .partial = reader.partial;
0297: this .hybridXref = reader.hybridXref;
0298: this .objStmToOffset = reader.objStmToOffset;
0299: this .xref = reader.xref;
0300: this .cryptoRef = (PRIndirectReference) duplicatePdfObject(
0301: reader.cryptoRef, this );
0302: this .ownerPasswordUsed = reader.ownerPasswordUsed;
0303: }
0304:
0305: /** Gets a new file instance of the original PDF
0306: * document.
0307: * @return a new file instance of the original PDF document
0308: */
0309: public RandomAccessFileOrArray getSafeFile() {
0310: return tokens.getSafeFile();
0311: }
0312:
0313: protected PdfReaderInstance getPdfReaderInstance(PdfWriter writer) {
0314: return new PdfReaderInstance(this , writer);
0315: }
0316:
0317: /** Gets the number of pages in the document.
0318: * @return the number of pages in the document
0319: */
0320: public int getNumberOfPages() {
0321: return pageRefs.size();
0322: }
0323:
0324: /** Returns the document's catalog. This dictionary is not a copy,
0325: * any changes will be reflected in the catalog.
0326: * @return the document's catalog
0327: */
0328: public PdfDictionary getCatalog() {
0329: return catalog;
0330: }
0331:
0332: /** Returns the document's acroform, if it has one.
0333: * @return the document's acroform
0334: */
0335: public PRAcroForm getAcroForm() {
0336: if (!acroFormParsed) {
0337: acroFormParsed = true;
0338: PdfObject form = catalog.get(PdfName.ACROFORM);
0339: if (form != null) {
0340: try {
0341: acroForm = new PRAcroForm(this );
0342: acroForm
0343: .readAcroForm((PdfDictionary) getPdfObject(form));
0344: } catch (Exception e) {
0345: acroForm = null;
0346: }
0347: }
0348: }
0349: return acroForm;
0350: }
0351:
0352: /**
0353: * Gets the page rotation. This value can be 0, 90, 180 or 270.
0354: * @param index the page number. The first page is 1
0355: * @return the page rotation
0356: */
0357: public int getPageRotation(int index) {
0358: return getPageRotation(pageRefs.getPageNRelease(index));
0359: }
0360:
0361: int getPageRotation(PdfDictionary page) {
0362: PdfNumber rotate = (PdfNumber) getPdfObject(page
0363: .get(PdfName.ROTATE));
0364: if (rotate == null)
0365: return 0;
0366: else {
0367: int n = rotate.intValue();
0368: n %= 360;
0369: return n < 0 ? n + 360 : n;
0370: }
0371: }
0372:
0373: /** Gets the page size, taking rotation into account. This
0374: * is a <CODE>Rectangle</CODE> with the value of the /MediaBox and the /Rotate key.
0375: * @param index the page number. The first page is 1
0376: * @return a <CODE>Rectangle</CODE>
0377: */
0378: public Rectangle getPageSizeWithRotation(int index) {
0379: return getPageSizeWithRotation(pageRefs.getPageNRelease(index));
0380: }
0381:
0382: /**
0383: * Gets the rotated page from a page dictionary.
0384: * @param page the page dictionary
0385: * @return the rotated page
0386: */
0387: public Rectangle getPageSizeWithRotation(PdfDictionary page) {
0388: Rectangle rect = getPageSize(page);
0389: int rotation = getPageRotation(page);
0390: while (rotation > 0) {
0391: rect = rect.rotate();
0392: rotation -= 90;
0393: }
0394: return rect;
0395: }
0396:
0397: /** Gets the page size without taking rotation into account. This
0398: * is the value of the /MediaBox key.
0399: * @param index the page number. The first page is 1
0400: * @return the page size
0401: */
0402: public Rectangle getPageSize(int index) {
0403: return getPageSize(pageRefs.getPageNRelease(index));
0404: }
0405:
0406: /**
0407: * Gets the page from a page dictionary
0408: * @param page the page dictionary
0409: * @return the page
0410: */
0411: public Rectangle getPageSize(PdfDictionary page) {
0412: PdfArray mediaBox = (PdfArray) getPdfObject(page
0413: .get(PdfName.MEDIABOX));
0414: return getNormalizedRectangle(mediaBox);
0415: }
0416:
0417: /** Gets the crop box without taking rotation into account. This
0418: * is the value of the /CropBox key. The crop box is the part
0419: * of the document to be displayed or printed. It usually is the same
0420: * as the media box but may be smaller. If the page doesn't have a crop
0421: * box the page size will be returned.
0422: * @param index the page number. The first page is 1
0423: * @return the crop box
0424: */
0425: public Rectangle getCropBox(int index) {
0426: PdfDictionary page = pageRefs.getPageNRelease(index);
0427: PdfArray cropBox = (PdfArray) getPdfObjectRelease(page
0428: .get(PdfName.CROPBOX));
0429: if (cropBox == null)
0430: return getPageSize(page);
0431: return getNormalizedRectangle(cropBox);
0432: }
0433:
0434: /** Gets the box size. Allowed names are: "crop", "trim", "art", "bleed" and "media".
0435: * @param index the page number. The first page is 1
0436: * @param boxName the box name
0437: * @return the box rectangle or null
0438: */
0439: public Rectangle getBoxSize(int index, String boxName) {
0440: PdfDictionary page = pageRefs.getPageNRelease(index);
0441: PdfArray box = null;
0442: if (boxName.equals("trim"))
0443: box = (PdfArray) getPdfObjectRelease(page
0444: .get(PdfName.TRIMBOX));
0445: else if (boxName.equals("art"))
0446: box = (PdfArray) getPdfObjectRelease(page
0447: .get(PdfName.ARTBOX));
0448: else if (boxName.equals("bleed"))
0449: box = (PdfArray) getPdfObjectRelease(page
0450: .get(PdfName.BLEEDBOX));
0451: else if (boxName.equals("crop"))
0452: box = (PdfArray) getPdfObjectRelease(page
0453: .get(PdfName.CROPBOX));
0454: else if (boxName.equals("media"))
0455: box = (PdfArray) getPdfObjectRelease(page
0456: .get(PdfName.MEDIABOX));
0457: if (box == null)
0458: return null;
0459: return getNormalizedRectangle(box);
0460: }
0461:
0462: /** Returns the content of the document information dictionary as a <CODE>HashMap</CODE>
0463: * of <CODE>String</CODE>.
0464: * @return content of the document information dictionary
0465: */
0466: public HashMap getInfo() {
0467: HashMap map = new HashMap();
0468: PdfDictionary info = (PdfDictionary) getPdfObject(trailer
0469: .get(PdfName.INFO));
0470: if (info == null)
0471: return map;
0472: for (Iterator it = info.getKeys().iterator(); it.hasNext();) {
0473: PdfName key = (PdfName) it.next();
0474: PdfObject obj = getPdfObject(info.get(key));
0475: if (obj == null)
0476: continue;
0477: String value = obj.toString();
0478: switch (obj.type()) {
0479: case PdfObject.STRING: {
0480: value = ((PdfString) obj).toUnicodeString();
0481: break;
0482: }
0483: case PdfObject.NAME: {
0484: value = PdfName.decodeName(value);
0485: break;
0486: }
0487: }
0488: map.put(PdfName.decodeName(key.toString()), value);
0489: }
0490: return map;
0491: }
0492:
0493: /** Normalizes a <CODE>Rectangle</CODE> so that llx and lly are smaller than urx and ury.
0494: * @param box the original rectangle
0495: * @return a normalized <CODE>Rectangle</CODE>
0496: */
0497: public static Rectangle getNormalizedRectangle(PdfArray box) {
0498: ArrayList rect = box.getArrayList();
0499: float llx = ((PdfNumber) getPdfObjectRelease((PdfObject) rect
0500: .get(0))).floatValue();
0501: float lly = ((PdfNumber) getPdfObjectRelease((PdfObject) rect
0502: .get(1))).floatValue();
0503: float urx = ((PdfNumber) getPdfObjectRelease((PdfObject) rect
0504: .get(2))).floatValue();
0505: float ury = ((PdfNumber) getPdfObjectRelease((PdfObject) rect
0506: .get(3))).floatValue();
0507: return new Rectangle(Math.min(llx, urx), Math.min(lly, ury),
0508: Math.max(llx, urx), Math.max(lly, ury));
0509: }
0510:
0511: protected void readPdf() throws IOException {
0512: try {
0513: fileLength = tokens.getFile().length();
0514: pdfVersion = tokens.checkPdfHeader();
0515: try {
0516: readXref();
0517: } catch (Exception e) {
0518: try {
0519: rebuilt = true;
0520: rebuildXref();
0521: lastXref = -1;
0522: } catch (Exception ne) {
0523: throw new IOException("Rebuild failed: "
0524: + ne.getMessage() + "; Original message: "
0525: + e.getMessage());
0526: }
0527: }
0528: try {
0529: readDocObj();
0530: } catch (Exception ne) {
0531: if (rebuilt)
0532: throw new IOException(ne.getMessage());
0533: rebuilt = true;
0534: encrypted = false;
0535: rebuildXref();
0536: lastXref = -1;
0537: readDocObj();
0538: }
0539:
0540: strings.clear();
0541: readPages();
0542: eliminateSharedStreams();
0543: removeUnusedObjects();
0544: } finally {
0545: try {
0546: tokens.close();
0547: } catch (Exception e) {
0548: // empty on purpose
0549: }
0550: }
0551: }
0552:
0553: protected void readPdfPartial() throws IOException {
0554: try {
0555: fileLength = tokens.getFile().length();
0556: pdfVersion = tokens.checkPdfHeader();
0557: try {
0558: readXref();
0559: } catch (Exception e) {
0560: try {
0561: rebuilt = true;
0562: rebuildXref();
0563: lastXref = -1;
0564: } catch (Exception ne) {
0565: throw new IOException("Rebuild failed: "
0566: + ne.getMessage() + "; Original message: "
0567: + e.getMessage());
0568: }
0569: }
0570: readDocObjPartial();
0571: readPages();
0572: } catch (IOException e) {
0573: try {
0574: tokens.close();
0575: } catch (Exception ee) {
0576: }
0577: throw e;
0578: }
0579: }
0580:
0581: private boolean equalsArray(byte ar1[], byte ar2[], int size) {
0582: for (int k = 0; k < size; ++k) {
0583: if (ar1[k] != ar2[k])
0584: return false;
0585: }
0586: return true;
0587: }
0588:
0589: /**
0590: * @throws IOException
0591: */
0592: private void readDecryptedDocObj() throws IOException {
0593: if (encrypted)
0594: return;
0595: PdfObject encDic = trailer.get(PdfName.ENCRYPT);
0596: if (encDic == null || encDic.toString().equals("null"))
0597: return;
0598: byte[] encryptionKey = null;
0599: encrypted = true;
0600: PdfDictionary enc = (PdfDictionary) getPdfObject(encDic);
0601:
0602: String s;
0603: PdfObject o;
0604:
0605: PdfArray documentIDs = (PdfArray) getPdfObject(trailer
0606: .get(PdfName.ID));
0607: byte documentID[] = null;
0608: if (documentIDs != null) {
0609: o = (PdfObject) documentIDs.getArrayList().get(0);
0610: strings.remove(o);
0611: s = o.toString();
0612: documentID = com.lowagie.text.DocWriter.getISOBytes(s);
0613: if (documentIDs.size() > 1)
0614: strings.remove(documentIDs.getArrayList().get(1));
0615: }
0616:
0617: byte uValue[] = null;
0618: byte oValue[] = null;
0619: int cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
0620: int lengthValue = 0;
0621:
0622: PdfObject filter = getPdfObjectRelease(enc.get(PdfName.FILTER));
0623:
0624: if (filter.equals(PdfName.STANDARD)) {
0625: s = enc.get(PdfName.U).toString();
0626: strings.remove(enc.get(PdfName.U));
0627: uValue = com.lowagie.text.DocWriter.getISOBytes(s);
0628: s = enc.get(PdfName.O).toString();
0629: strings.remove(enc.get(PdfName.O));
0630: oValue = com.lowagie.text.DocWriter.getISOBytes(s);
0631:
0632: o = enc.get(PdfName.R);
0633: if (!o.isNumber())
0634: throw new IOException("Illegal R value.");
0635: rValue = ((PdfNumber) o).intValue();
0636: if (rValue != 2 && rValue != 3 && rValue != 4)
0637: throw new IOException("Unknown encryption type ("
0638: + rValue + ")");
0639:
0640: o = enc.get(PdfName.P);
0641: if (!o.isNumber())
0642: throw new IOException("Illegal P value.");
0643: pValue = ((PdfNumber) o).intValue();
0644:
0645: if (rValue == 3) {
0646: o = enc.get(PdfName.LENGTH);
0647: if (!o.isNumber())
0648: throw new IOException("Illegal Length value.");
0649: lengthValue = ((PdfNumber) o).intValue();
0650: if (lengthValue > 128 || lengthValue < 40
0651: || lengthValue % 8 != 0)
0652: throw new IOException("Illegal Length value.");
0653: cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
0654: } else if (rValue == 4) {
0655: PdfDictionary dic = (PdfDictionary) enc.get(PdfName.CF);
0656: if (dic == null)
0657: throw new IOException("/CF not found (encryption)");
0658: dic = (PdfDictionary) dic.get(PdfName.STDCF);
0659: if (dic == null)
0660: throw new IOException(
0661: "/StdCF not found (encryption)");
0662: if (PdfName.V2.equals(dic.get(PdfName.CFM)))
0663: cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
0664: else if (PdfName.AESV2.equals(dic.get(PdfName.CFM)))
0665: cryptoMode = PdfWriter.ENCRYPTION_AES_128;
0666: else
0667: throw new IOException(
0668: "No compatible encryption found");
0669: PdfObject em = enc.get(PdfName.ENCRYPTMETADATA);
0670: if (em != null && em.toString().equals("false"))
0671: cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
0672: } else {
0673: cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
0674: }
0675: } else if (filter.equals(PdfName.PUBSEC)) {
0676: boolean foundRecipient = false;
0677: byte[] envelopedData = null;
0678: PdfArray recipients = null;
0679:
0680: o = enc.get(PdfName.V);
0681: if (!o.isNumber())
0682: throw new IOException("Illegal V value.");
0683: int vValue = ((PdfNumber) o).intValue();
0684: if (vValue != 1 && vValue != 2 && vValue != 4)
0685: throw new IOException("Unknown encryption type V = "
0686: + rValue);
0687:
0688: if (vValue == 2) {
0689: o = enc.get(PdfName.LENGTH);
0690: if (!o.isNumber())
0691: throw new IOException("Illegal Length value.");
0692: lengthValue = ((PdfNumber) o).intValue();
0693: if (lengthValue > 128 || lengthValue < 40
0694: || lengthValue % 8 != 0)
0695: throw new IOException("Illegal Length value.");
0696: cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
0697: recipients = (PdfArray) enc.get(PdfName.RECIPIENTS);
0698: } else if (vValue == 4) {
0699: PdfDictionary dic = (PdfDictionary) enc.get(PdfName.CF);
0700: if (dic == null)
0701: throw new IOException("/CF not found (encryption)");
0702: dic = (PdfDictionary) dic
0703: .get(PdfName.DEFAULTCRYPTFILER);
0704: if (dic == null)
0705: throw new IOException(
0706: "/DefaultCryptFilter not found (encryption)");
0707: if (PdfName.V2.equals(dic.get(PdfName.CFM))) {
0708: cryptoMode = PdfWriter.STANDARD_ENCRYPTION_128;
0709: lengthValue = 128;
0710: } else if (PdfName.AESV2.equals(dic.get(PdfName.CFM))) {
0711: cryptoMode = PdfWriter.ENCRYPTION_AES_128;
0712: lengthValue = 128;
0713: } else
0714: throw new IOException(
0715: "No compatible encryption found");
0716: PdfObject em = dic.get(PdfName.ENCRYPTMETADATA);
0717: if (em != null && em.toString().equals("false"))
0718: cryptoMode |= PdfWriter.DO_NOT_ENCRYPT_METADATA;
0719:
0720: recipients = (PdfArray) dic.get(PdfName.RECIPIENTS);
0721: } else {
0722: cryptoMode = PdfWriter.STANDARD_ENCRYPTION_40;
0723: lengthValue = 40;
0724: recipients = (PdfArray) enc.get(PdfName.RECIPIENTS);
0725: }
0726:
0727: for (int i = 0; i < recipients.size(); i++) {
0728: PdfObject recipient = (PdfObject) recipients
0729: .getArrayList().get(i);
0730: strings.remove(recipient);
0731:
0732: CMSEnvelopedData data = null;
0733: try {
0734: data = new CMSEnvelopedData(recipient.getBytes());
0735:
0736: Iterator recipientCertificatesIt = data
0737: .getRecipientInfos().getRecipients()
0738: .iterator();
0739:
0740: while (recipientCertificatesIt.hasNext()) {
0741: RecipientInformation recipientInfo = (RecipientInformation) recipientCertificatesIt
0742: .next();
0743:
0744: if (recipientInfo.getRID().match(certificate)
0745: && !foundRecipient) {
0746:
0747: envelopedData = recipientInfo.getContent(
0748: certificateKey,
0749: certificateKeyProvider);
0750: foundRecipient = true;
0751: }
0752: }
0753: } catch (Exception f) {
0754: throw new ExceptionConverter(f);
0755: }
0756: }
0757:
0758: if (!foundRecipient || envelopedData == null) {
0759: throw new IOException("Bad certificate and key.");
0760: }
0761:
0762: MessageDigest md = null;
0763:
0764: try {
0765: md = MessageDigest.getInstance("SHA-1");
0766: md.update(envelopedData, 0, 20);
0767: for (int i = 0; i < recipients.size(); i++) {
0768: byte[] encodedRecipient = ((PdfObject) recipients
0769: .getArrayList().get(i)).getBytes();
0770: md.update(encodedRecipient);
0771: }
0772: if ((cryptoMode & PdfWriter.DO_NOT_ENCRYPT_METADATA) != 0)
0773: md.update(new byte[] { (byte) 255, (byte) 255,
0774: (byte) 255, (byte) 255 });
0775: encryptionKey = md.digest();
0776:
0777: } catch (Exception f) {
0778: throw new ExceptionConverter(f);
0779: }
0780: }
0781:
0782: decrypt = new PdfEncryption();
0783: decrypt.setCryptoMode(cryptoMode, lengthValue);
0784:
0785: if (filter.equals(PdfName.STANDARD)) {
0786: //check by owner password
0787: decrypt.setupByOwnerPassword(documentID, password, uValue,
0788: oValue, pValue);
0789: if (!equalsArray(uValue, decrypt.userKey,
0790: (rValue == 3 || rValue == 4) ? 16 : 32)) {
0791: //check by user password
0792: decrypt.setupByUserPassword(documentID, password,
0793: oValue, pValue);
0794: if (!equalsArray(uValue, decrypt.userKey,
0795: (rValue == 3 || rValue == 4) ? 16 : 32)) {
0796: throw new IOException("Bad user password");
0797: }
0798: } else
0799: ownerPasswordUsed = true;
0800: } else if (filter.equals(PdfName.PUBSEC)) {
0801: decrypt.setupByEncryptionKey(encryptionKey, lengthValue);
0802: ownerPasswordUsed = true;
0803: }
0804:
0805: for (int k = 0; k < strings.size(); ++k) {
0806: PdfString str = (PdfString) strings.get(k);
0807: str.decrypt(this );
0808: }
0809:
0810: if (encDic.isIndirect()) {
0811: cryptoRef = (PRIndirectReference) encDic;
0812: xrefObj.set(cryptoRef.getNumber(), null);
0813: }
0814: }
0815:
0816: /**
0817: * @param obj
0818: * @return a PdfObject
0819: */
0820: public static PdfObject getPdfObjectRelease(PdfObject obj) {
0821: PdfObject obj2 = getPdfObject(obj);
0822: releaseLastXrefPartial(obj);
0823: return obj2;
0824: }
0825:
0826: /**
0827: * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
0828: * if needed.
0829: * @param obj the <CODE>PdfObject</CODE> to read
0830: * @return the resolved <CODE>PdfObject</CODE>
0831: */
0832: public static PdfObject getPdfObject(PdfObject obj) {
0833: if (obj == null)
0834: return null;
0835: if (!obj.isIndirect())
0836: return obj;
0837: try {
0838: PRIndirectReference ref = (PRIndirectReference) obj;
0839: int idx = ref.getNumber();
0840: boolean appendable = ref.getReader().appendable;
0841: obj = ref.getReader().getPdfObject(idx);
0842: if (obj == null) {
0843: return null;
0844: } else {
0845: if (appendable) {
0846: switch (obj.type()) {
0847: case PdfObject.NULL:
0848: obj = new PdfNull();
0849: break;
0850: case PdfObject.BOOLEAN:
0851: obj = new PdfBoolean(((PdfBoolean) obj)
0852: .booleanValue());
0853: break;
0854: case PdfObject.NAME:
0855: obj = new PdfName(obj.getBytes());
0856: break;
0857: }
0858: obj.setIndRef(ref);
0859: }
0860: return obj;
0861: }
0862: } catch (Exception e) {
0863: throw new ExceptionConverter(e);
0864: }
0865: }
0866:
0867: /**
0868: * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
0869: * if needed. If the reader was opened in partial mode the object will be released
0870: * to save memory.
0871: * @param obj the <CODE>PdfObject</CODE> to read
0872: * @param parent
0873: * @return a PdfObject
0874: */
0875: public static PdfObject getPdfObjectRelease(PdfObject obj,
0876: PdfObject parent) {
0877: PdfObject obj2 = getPdfObject(obj, parent);
0878: releaseLastXrefPartial(obj);
0879: return obj2;
0880: }
0881:
0882: /**
0883: * @param obj
0884: * @param parent
0885: * @return a PdfObject
0886: */
0887: public static PdfObject getPdfObject(PdfObject obj, PdfObject parent) {
0888: if (obj == null)
0889: return null;
0890: if (!obj.isIndirect()) {
0891: PRIndirectReference ref = null;
0892: if (parent != null && (ref = parent.getIndRef()) != null
0893: && ref.getReader().isAppendable()) {
0894: switch (obj.type()) {
0895: case PdfObject.NULL:
0896: obj = new PdfNull();
0897: break;
0898: case PdfObject.BOOLEAN:
0899: obj = new PdfBoolean(((PdfBoolean) obj)
0900: .booleanValue());
0901: break;
0902: case PdfObject.NAME:
0903: obj = new PdfName(obj.getBytes());
0904: break;
0905: }
0906: obj.setIndRef(ref);
0907: }
0908: return obj;
0909: }
0910: return getPdfObject(obj);
0911: }
0912:
0913: /**
0914: * @param idx
0915: * @return a PdfObject
0916: */
0917: public PdfObject getPdfObjectRelease(int idx) {
0918: PdfObject obj = getPdfObject(idx);
0919: releaseLastXrefPartial();
0920: return obj;
0921: }
0922:
0923: /**
0924: * @param idx
0925: * @return aPdfObject
0926: */
0927: public PdfObject getPdfObject(int idx) {
0928: try {
0929: lastXrefPartial = -1;
0930: if (idx < 0 || idx >= xrefObj.size())
0931: return null;
0932: PdfObject obj = (PdfObject) xrefObj.get(idx);
0933: if (!partial || obj != null)
0934: return obj;
0935: if (idx * 2 >= xref.length)
0936: return null;
0937: obj = readSingleObject(idx);
0938: lastXrefPartial = -1;
0939: if (obj != null)
0940: lastXrefPartial = idx;
0941: return obj;
0942: } catch (Exception e) {
0943: throw new ExceptionConverter(e);
0944: }
0945: }
0946:
0947: /**
0948: *
0949: */
0950: public void resetLastXrefPartial() {
0951: lastXrefPartial = -1;
0952: }
0953:
0954: /**
0955: *
0956: */
0957: public void releaseLastXrefPartial() {
0958: if (partial && lastXrefPartial != -1) {
0959: xrefObj.set(lastXrefPartial, null);
0960: lastXrefPartial = -1;
0961: }
0962: }
0963:
0964: /**
0965: * @param obj
0966: */
0967: public static void releaseLastXrefPartial(PdfObject obj) {
0968: if (obj == null)
0969: return;
0970: if (!obj.isIndirect())
0971: return;
0972: PRIndirectReference ref = (PRIndirectReference) obj;
0973: PdfReader reader = ref.getReader();
0974: if (reader.partial && reader.lastXrefPartial != -1
0975: && reader.lastXrefPartial == ref.getNumber()) {
0976: reader.xrefObj.set(reader.lastXrefPartial, null);
0977: }
0978: reader.lastXrefPartial = -1;
0979: }
0980:
0981: private void setXrefPartialObject(int idx, PdfObject obj) {
0982: if (!partial || idx < 0)
0983: return;
0984: xrefObj.set(idx, obj);
0985: }
0986:
0987: /**
0988: * @param obj
0989: * @return an indirect reference
0990: */
0991: public PRIndirectReference addPdfObject(PdfObject obj) {
0992: xrefObj.add(obj);
0993: return new PRIndirectReference(this , xrefObj.size() - 1);
0994: }
0995:
0996: protected void readPages() throws IOException {
0997: catalog = (PdfDictionary) getPdfObject(trailer
0998: .get(PdfName.ROOT));
0999: rootPages = (PdfDictionary) getPdfObject(catalog
1000: .get(PdfName.PAGES));
1001: pageRefs = new PageRefs(this );
1002: }
1003:
1004: protected void readDocObjPartial() throws IOException {
1005: xrefObj = new ArrayList(xref.length / 2);
1006: xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
1007: readDecryptedDocObj();
1008: if (objStmToOffset != null) {
1009: int keys[] = objStmToOffset.getKeys();
1010: for (int k = 0; k < keys.length; ++k) {
1011: int n = keys[k];
1012: objStmToOffset.put(n, xref[n * 2]);
1013: xref[n * 2] = -1;
1014: }
1015: }
1016: }
1017:
1018: protected PdfObject readSingleObject(int k) throws IOException {
1019: strings.clear();
1020: int k2 = k * 2;
1021: int pos = xref[k2];
1022: if (pos < 0)
1023: return null;
1024: if (xref[k2 + 1] > 0)
1025: pos = objStmToOffset.get(xref[k2 + 1]);
1026: if (pos == 0)
1027: return null;
1028: tokens.seek(pos);
1029: tokens.nextValidToken();
1030: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1031: tokens.throwError("Invalid object number.");
1032: objNum = tokens.intValue();
1033: tokens.nextValidToken();
1034: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1035: tokens.throwError("Invalid generation number.");
1036: objGen = tokens.intValue();
1037: tokens.nextValidToken();
1038: if (!tokens.getStringValue().equals("obj"))
1039: tokens.throwError("Token 'obj' expected.");
1040: PdfObject obj;
1041: try {
1042: obj = readPRObject();
1043: for (int j = 0; j < strings.size(); ++j) {
1044: PdfString str = (PdfString) strings.get(j);
1045: str.decrypt(this );
1046: }
1047: if (obj.isStream()) {
1048: checkPRStreamLength((PRStream) obj);
1049: }
1050: } catch (Exception e) {
1051: obj = null;
1052: }
1053: if (xref[k2 + 1] > 0) {
1054: obj = readOneObjStm((PRStream) obj, xref[k2]);
1055: }
1056: xrefObj.set(k, obj);
1057: return obj;
1058: }
1059:
1060: protected PdfObject readOneObjStm(PRStream stream, int idx)
1061: throws IOException {
1062: int first = ((PdfNumber) getPdfObject(stream.get(PdfName.FIRST)))
1063: .intValue();
1064: byte b[] = getStreamBytes(stream, tokens.getFile());
1065: PRTokeniser saveTokens = tokens;
1066: tokens = new PRTokeniser(b);
1067: try {
1068: int address = 0;
1069: boolean ok = true;
1070: ++idx;
1071: for (int k = 0; k < idx; ++k) {
1072: ok = tokens.nextToken();
1073: if (!ok)
1074: break;
1075: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1076: ok = false;
1077: break;
1078: }
1079: ok = tokens.nextToken();
1080: if (!ok)
1081: break;
1082: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1083: ok = false;
1084: break;
1085: }
1086: address = tokens.intValue() + first;
1087: }
1088: if (!ok)
1089: throw new IOException("Error reading ObjStm");
1090: tokens.seek(address);
1091: return readPRObject();
1092: } finally {
1093: tokens = saveTokens;
1094: }
1095: }
1096:
1097: /**
1098: * @return the percentage of the cross reference table that has been read
1099: */
1100: public double dumpPerc() {
1101: int total = 0;
1102: for (int k = 0; k < xrefObj.size(); ++k) {
1103: if (xrefObj.get(k) != null)
1104: ++total;
1105: }
1106: return (total * 100.0 / xrefObj.size());
1107: }
1108:
1109: protected void readDocObj() throws IOException {
1110: ArrayList streams = new ArrayList();
1111: xrefObj = new ArrayList(xref.length / 2);
1112: xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
1113: for (int k = 2; k < xref.length; k += 2) {
1114: int pos = xref[k];
1115: if (pos <= 0 || xref[k + 1] > 0)
1116: continue;
1117: tokens.seek(pos);
1118: tokens.nextValidToken();
1119: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1120: tokens.throwError("Invalid object number.");
1121: objNum = tokens.intValue();
1122: tokens.nextValidToken();
1123: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1124: tokens.throwError("Invalid generation number.");
1125: objGen = tokens.intValue();
1126: tokens.nextValidToken();
1127: if (!tokens.getStringValue().equals("obj"))
1128: tokens.throwError("Token 'obj' expected.");
1129: PdfObject obj;
1130: try {
1131: obj = readPRObject();
1132: if (obj.isStream()) {
1133: streams.add(obj);
1134: }
1135: } catch (Exception e) {
1136: obj = null;
1137: }
1138: xrefObj.set(k / 2, obj);
1139: }
1140: for (int k = 0; k < streams.size(); ++k) {
1141: checkPRStreamLength((PRStream) streams.get(k));
1142: }
1143: readDecryptedDocObj();
1144: if (objStmMark != null) {
1145: for (Iterator i = objStmMark.entrySet().iterator(); i
1146: .hasNext();) {
1147: Map.Entry entry = (Map.Entry) i.next();
1148: int n = ((Integer) entry.getKey()).intValue();
1149: IntHashtable h = (IntHashtable) entry.getValue();
1150: readObjStm((PRStream) xrefObj.get(n), h);
1151: xrefObj.set(n, null);
1152: }
1153: objStmMark = null;
1154: }
1155: xref = null;
1156: }
1157:
1158: private void checkPRStreamLength(PRStream stream)
1159: throws IOException {
1160: int fileLength = tokens.length();
1161: int start = stream.getOffset();
1162: boolean calc = false;
1163: int streamLength = 0;
1164: PdfObject obj = getPdfObjectRelease(stream.get(PdfName.LENGTH));
1165: if (obj != null && obj.type() == PdfObject.NUMBER) {
1166: streamLength = ((PdfNumber) obj).intValue();
1167: if (streamLength + start > fileLength - 20)
1168: calc = true;
1169: else {
1170: tokens.seek(start + streamLength);
1171: String line = tokens.readString(20);
1172: if (!line.startsWith("\nendstream")
1173: && !line.startsWith("\r\nendstream")
1174: && !line.startsWith("\rendstream")
1175: && !line.startsWith("endstream"))
1176: calc = true;
1177: }
1178: } else
1179: calc = true;
1180: if (calc) {
1181: byte tline[] = new byte[16];
1182: tokens.seek(start);
1183: while (true) {
1184: int pos = tokens.getFilePointer();
1185: if (!tokens.readLineSegment(tline))
1186: break;
1187: if (equalsn(tline, endstream)) {
1188: streamLength = pos - start;
1189: break;
1190: }
1191: if (equalsn(tline, endobj)) {
1192: tokens.seek(pos - 16);
1193: String s = tokens.readString(16);
1194: int index = s.indexOf("endstream");
1195: if (index >= 0)
1196: pos = pos - 16 + index;
1197: streamLength = pos - start;
1198: break;
1199: }
1200: }
1201: }
1202: stream.setLength(streamLength);
1203: }
1204:
1205: protected void readObjStm(PRStream stream, IntHashtable map)
1206: throws IOException {
1207: int first = ((PdfNumber) getPdfObject(stream.get(PdfName.FIRST)))
1208: .intValue();
1209: int n = ((PdfNumber) getPdfObject(stream.get(PdfName.N)))
1210: .intValue();
1211: byte b[] = getStreamBytes(stream, tokens.getFile());
1212: PRTokeniser saveTokens = tokens;
1213: tokens = new PRTokeniser(b);
1214: try {
1215: int address[] = new int[n];
1216: int objNumber[] = new int[n];
1217: boolean ok = true;
1218: for (int k = 0; k < n; ++k) {
1219: ok = tokens.nextToken();
1220: if (!ok)
1221: break;
1222: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1223: ok = false;
1224: break;
1225: }
1226: objNumber[k] = tokens.intValue();
1227: ok = tokens.nextToken();
1228: if (!ok)
1229: break;
1230: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
1231: ok = false;
1232: break;
1233: }
1234: address[k] = tokens.intValue() + first;
1235: }
1236: if (!ok)
1237: throw new IOException("Error reading ObjStm");
1238: for (int k = 0; k < n; ++k) {
1239: if (map.containsKey(k)) {
1240: tokens.seek(address[k]);
1241: PdfObject obj = readPRObject();
1242: xrefObj.set(objNumber[k], obj);
1243: }
1244: }
1245: } finally {
1246: tokens = saveTokens;
1247: }
1248: }
1249:
1250: /**
1251: * Eliminates the reference to the object freeing the memory used by it and clearing
1252: * the xref entry.
1253: * @param obj the object. If it's an indirect reference it will be eliminated
1254: * @return the object or the already erased dereferenced object
1255: */
1256: public static PdfObject killIndirect(PdfObject obj) {
1257: if (obj == null || obj.isNull())
1258: return null;
1259: PdfObject ret = getPdfObjectRelease(obj);
1260: if (obj.isIndirect()) {
1261: PRIndirectReference ref = (PRIndirectReference) obj;
1262: PdfReader reader = ref.getReader();
1263: int n = ref.getNumber();
1264: reader.xrefObj.set(n, null);
1265: if (reader.partial)
1266: reader.xref[n * 2] = -1;
1267: }
1268: return ret;
1269: }
1270:
1271: private void ensureXrefSize(int size) {
1272: if (size == 0)
1273: return;
1274: if (xref == null)
1275: xref = new int[size];
1276: else {
1277: if (xref.length < size) {
1278: int xref2[] = new int[size];
1279: System.arraycopy(xref, 0, xref2, 0, xref.length);
1280: xref = xref2;
1281: }
1282: }
1283: }
1284:
1285: protected void readXref() throws IOException {
1286: hybridXref = false;
1287: newXrefType = false;
1288: tokens.seek(tokens.getStartxref());
1289: tokens.nextToken();
1290: if (!tokens.getStringValue().equals("startxref"))
1291: throw new IOException("startxref not found.");
1292: tokens.nextToken();
1293: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1294: throw new IOException(
1295: "startxref is not followed by a number.");
1296: int startxref = tokens.intValue();
1297: lastXref = startxref;
1298: eofPos = tokens.getFilePointer();
1299: try {
1300: if (readXRefStream(startxref)) {
1301: newXrefType = true;
1302: return;
1303: }
1304: } catch (Exception e) {
1305: }
1306: xref = null;
1307: tokens.seek(startxref);
1308: trailer = readXrefSection();
1309: PdfDictionary trailer2 = trailer;
1310: while (true) {
1311: PdfNumber prev = (PdfNumber) trailer2.get(PdfName.PREV);
1312: if (prev == null)
1313: break;
1314: tokens.seek(prev.intValue());
1315: trailer2 = readXrefSection();
1316: }
1317: }
1318:
1319: protected PdfDictionary readXrefSection() throws IOException {
1320: tokens.nextValidToken();
1321: if (!tokens.getStringValue().equals("xref"))
1322: tokens.throwError("xref subsection not found");
1323: int start = 0;
1324: int end = 0;
1325: int pos = 0;
1326: int gen = 0;
1327: while (true) {
1328: tokens.nextValidToken();
1329: if (tokens.getStringValue().equals("trailer"))
1330: break;
1331: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1332: tokens
1333: .throwError("Object number of the first object in this xref subsection not found");
1334: start = tokens.intValue();
1335: tokens.nextValidToken();
1336: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1337: tokens
1338: .throwError("Number of entries in this xref subsection not found");
1339: end = tokens.intValue() + start;
1340: if (start == 1) { // fix incorrect start number
1341: int back = tokens.getFilePointer();
1342: tokens.nextValidToken();
1343: pos = tokens.intValue();
1344: tokens.nextValidToken();
1345: gen = tokens.intValue();
1346: if (pos == 0 && gen == 65535) {
1347: --start;
1348: --end;
1349: }
1350: tokens.seek(back);
1351: }
1352: ensureXrefSize(end * 2);
1353: for (int k = start; k < end; ++k) {
1354: tokens.nextValidToken();
1355: pos = tokens.intValue();
1356: tokens.nextValidToken();
1357: gen = tokens.intValue();
1358: tokens.nextValidToken();
1359: int p = k * 2;
1360: if (tokens.getStringValue().equals("n")) {
1361: if (xref[p] == 0 && xref[p + 1] == 0) {
1362: // if (pos == 0)
1363: // tokens.throwError("File position 0 cross-reference entry in this xref subsection");
1364: xref[p] = pos;
1365: }
1366: } else if (tokens.getStringValue().equals("f")) {
1367: if (xref[p] == 0 && xref[p + 1] == 0)
1368: xref[p] = -1;
1369: } else
1370: tokens
1371: .throwError("Invalid cross-reference entry in this xref subsection");
1372: }
1373: }
1374: PdfDictionary trailer = (PdfDictionary) readPRObject();
1375: PdfNumber xrefSize = (PdfNumber) trailer.get(PdfName.SIZE);
1376: ensureXrefSize(xrefSize.intValue() * 2);
1377: PdfObject xrs = trailer.get(PdfName.XREFSTM);
1378: if (xrs != null && xrs.isNumber()) {
1379: int loc = ((PdfNumber) xrs).intValue();
1380: try {
1381: readXRefStream(loc);
1382: newXrefType = true;
1383: hybridXref = true;
1384: } catch (IOException e) {
1385: xref = null;
1386: throw e;
1387: }
1388: }
1389: return trailer;
1390: }
1391:
1392: protected boolean readXRefStream(int ptr) throws IOException {
1393: tokens.seek(ptr);
1394: int this Stream = 0;
1395: if (!tokens.nextToken())
1396: return false;
1397: if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1398: return false;
1399: this Stream = tokens.intValue();
1400: if (!tokens.nextToken()
1401: || tokens.getTokenType() != PRTokeniser.TK_NUMBER)
1402: return false;
1403: if (!tokens.nextToken()
1404: || !tokens.getStringValue().equals("obj"))
1405: return false;
1406: PdfObject object = readPRObject();
1407: PRStream stm = null;
1408: if (object.isStream()) {
1409: stm = (PRStream) object;
1410: if (!PdfName.XREF.equals(stm.get(PdfName.TYPE)))
1411: return false;
1412: } else
1413: return false;
1414: if (trailer == null) {
1415: trailer = new PdfDictionary();
1416: trailer.putAll(stm);
1417: }
1418: stm.setLength(((PdfNumber) stm.get(PdfName.LENGTH)).intValue());
1419: int size = ((PdfNumber) stm.get(PdfName.SIZE)).intValue();
1420: PdfArray index;
1421: PdfObject obj = stm.get(PdfName.INDEX);
1422: if (obj == null) {
1423: index = new PdfArray();
1424: index.add(new int[] { 0, size });
1425: } else
1426: index = (PdfArray) obj;
1427: PdfArray w = (PdfArray) stm.get(PdfName.W);
1428: int prev = -1;
1429: obj = stm.get(PdfName.PREV);
1430: if (obj != null)
1431: prev = ((PdfNumber) obj).intValue();
1432: // Each xref pair is a position
1433: // type 0 -> -1, 0
1434: // type 1 -> offset, 0
1435: // type 2 -> index, obj num
1436: ensureXrefSize(size * 2);
1437: if (objStmMark == null && !partial)
1438: objStmMark = new HashMap();
1439: if (objStmToOffset == null && partial)
1440: objStmToOffset = new IntHashtable();
1441: byte b[] = getStreamBytes(stm, tokens.getFile());
1442: int bptr = 0;
1443: ArrayList wa = w.getArrayList();
1444: int wc[] = new int[3];
1445: for (int k = 0; k < 3; ++k)
1446: wc[k] = ((PdfNumber) wa.get(k)).intValue();
1447: ArrayList sections = index.getArrayList();
1448: for (int idx = 0; idx < sections.size(); idx += 2) {
1449: int start = ((PdfNumber) sections.get(idx)).intValue();
1450: int length = ((PdfNumber) sections.get(idx + 1)).intValue();
1451: ensureXrefSize((start + length) * 2);
1452: while (length-- > 0) {
1453: int type = 1;
1454: if (wc[0] > 0) {
1455: type = 0;
1456: for (int k = 0; k < wc[0]; ++k)
1457: type = (type << 8) + (b[bptr++] & 0xff);
1458: }
1459: int field2 = 0;
1460: for (int k = 0; k < wc[1]; ++k)
1461: field2 = (field2 << 8) + (b[bptr++] & 0xff);
1462: int field3 = 0;
1463: for (int k = 0; k < wc[2]; ++k)
1464: field3 = (field3 << 8) + (b[bptr++] & 0xff);
1465: int base = start * 2;
1466: if (xref[base] == 0 && xref[base + 1] == 0) {
1467: switch (type) {
1468: case 0:
1469: xref[base] = -1;
1470: break;
1471: case 1:
1472: xref[base] = field2;
1473: break;
1474: case 2:
1475: xref[base] = field3;
1476: xref[base + 1] = field2;
1477: if (partial) {
1478: objStmToOffset.put(field2, 0);
1479: } else {
1480: Integer on = new Integer(field2);
1481: IntHashtable seq = (IntHashtable) objStmMark
1482: .get(on);
1483: if (seq == null) {
1484: seq = new IntHashtable();
1485: seq.put(field3, 1);
1486: objStmMark.put(on, seq);
1487: } else
1488: seq.put(field3, 1);
1489: }
1490: break;
1491: }
1492: }
1493: ++start;
1494: }
1495: }
1496: this Stream *= 2;
1497: if (this Stream < xref.length)
1498: xref[this Stream] = -1;
1499:
1500: if (prev == -1)
1501: return true;
1502: return readXRefStream(prev);
1503: }
1504:
1505: protected void rebuildXref() throws IOException {
1506: hybridXref = false;
1507: newXrefType = false;
1508: tokens.seek(0);
1509: int xr[][] = new int[1024][];
1510: int top = 0;
1511: trailer = null;
1512: byte line[] = new byte[64];
1513: for (;;) {
1514: int pos = tokens.getFilePointer();
1515: if (!tokens.readLineSegment(line))
1516: break;
1517: if (line[0] == 't') {
1518: if (!PdfEncodings.convertToString(line, null)
1519: .startsWith("trailer"))
1520: continue;
1521: tokens.seek(pos);
1522: tokens.nextToken();
1523: pos = tokens.getFilePointer();
1524: try {
1525: PdfDictionary dic = (PdfDictionary) readPRObject();
1526: if (dic.get(PdfName.ROOT) != null)
1527: trailer = dic;
1528: else
1529: tokens.seek(pos);
1530: } catch (Exception e) {
1531: tokens.seek(pos);
1532: }
1533: } else if (line[0] >= '0' && line[0] <= '9') {
1534: int obj[] = PRTokeniser.checkObjectStart(line);
1535: if (obj == null)
1536: continue;
1537: int num = obj[0];
1538: int gen = obj[1];
1539: if (num >= xr.length) {
1540: int newLength = num * 2;
1541: int xr2[][] = new int[newLength][];
1542: System.arraycopy(xr, 0, xr2, 0, top);
1543: xr = xr2;
1544: }
1545: if (num >= top)
1546: top = num + 1;
1547: if (xr[num] == null || gen >= xr[num][1]) {
1548: obj[0] = pos;
1549: xr[num] = obj;
1550: }
1551: }
1552: }
1553: if (trailer == null)
1554: throw new IOException("trailer not found.");
1555: xref = new int[top * 2];
1556: for (int k = 0; k < top; ++k) {
1557: int obj[] = xr[k];
1558: if (obj != null)
1559: xref[k * 2] = obj[0];
1560: }
1561: }
1562:
1563: protected PdfDictionary readDictionary() throws IOException {
1564: PdfDictionary dic = new PdfDictionary();
1565: while (true) {
1566: tokens.nextValidToken();
1567: if (tokens.getTokenType() == PRTokeniser.TK_END_DIC)
1568: break;
1569: if (tokens.getTokenType() != PRTokeniser.TK_NAME)
1570: tokens.throwError("Dictionary key is not a name.");
1571: PdfName name = new PdfName(tokens.getStringValue(), false);
1572: PdfObject obj = readPRObject();
1573: int type = obj.type();
1574: if (-type == PRTokeniser.TK_END_DIC)
1575: tokens.throwError("Unexpected '>>'");
1576: if (-type == PRTokeniser.TK_END_ARRAY)
1577: tokens.throwError("Unexpected ']'");
1578: dic.put(name, obj);
1579: }
1580: return dic;
1581: }
1582:
1583: protected PdfArray readArray() throws IOException {
1584: PdfArray array = new PdfArray();
1585: while (true) {
1586: PdfObject obj = readPRObject();
1587: int type = obj.type();
1588: if (-type == PRTokeniser.TK_END_ARRAY)
1589: break;
1590: if (-type == PRTokeniser.TK_END_DIC)
1591: tokens.throwError("Unexpected '>>'");
1592: array.add(obj);
1593: }
1594: return array;
1595: }
1596:
1597: protected PdfObject readPRObject() throws IOException {
1598: tokens.nextValidToken();
1599: int type = tokens.getTokenType();
1600: switch (type) {
1601: case PRTokeniser.TK_START_DIC: {
1602: PdfDictionary dic = readDictionary();
1603: int pos = tokens.getFilePointer();
1604: // be careful in the trailer. May not be a "next" token.
1605: if (tokens.nextToken()
1606: && tokens.getStringValue().equals("stream")) {
1607: int ch = tokens.read();
1608: if (ch != '\n')
1609: ch = tokens.read();
1610: if (ch != '\n')
1611: tokens.backOnePosition(ch);
1612: PRStream stream = new PRStream(this , tokens
1613: .getFilePointer());
1614: stream.putAll(dic);
1615: stream.setObjNum(objNum, objGen);
1616: return stream;
1617: } else {
1618: tokens.seek(pos);
1619: return dic;
1620: }
1621: }
1622: case PRTokeniser.TK_START_ARRAY:
1623: return readArray();
1624: case PRTokeniser.TK_NUMBER:
1625: return new PdfNumber(tokens.getStringValue());
1626: case PRTokeniser.TK_STRING:
1627: PdfString str = new PdfString(tokens.getStringValue(), null)
1628: .setHexWriting(tokens.isHexString());
1629: str.setObjNum(objNum, objGen);
1630: if (strings != null)
1631: strings.add(str);
1632: return str;
1633: case PRTokeniser.TK_NAME:
1634: return new PdfName(tokens.getStringValue(), false);
1635: case PRTokeniser.TK_REF:
1636: int num = tokens.getReference();
1637: PRIndirectReference ref = new PRIndirectReference(this ,
1638: num, tokens.getGeneration());
1639: return ref;
1640: default:
1641: String sv = tokens.getStringValue();
1642: if ("null".equals(sv))
1643: return PdfNull.PDFNULL;
1644: else if ("true".equals(sv))
1645: return PdfBoolean.PDFTRUE;
1646: else if ("false".equals(sv))
1647: return PdfBoolean.PDFFALSE;
1648: return new PdfLiteral(-type, tokens.getStringValue());
1649: }
1650: }
1651:
1652: /** Decodes a stream that has the FlateDecode filter.
1653: * @param in the input data
1654: * @return the decoded data
1655: */
1656: public static byte[] FlateDecode(byte in[]) {
1657: byte b[] = FlateDecode(in, true);
1658: if (b == null)
1659: return FlateDecode(in, false);
1660: return b;
1661: }
1662:
1663: /**
1664: * @param in
1665: * @param dicPar
1666: * @return a byte array
1667: */
1668: public static byte[] decodePredictor(byte in[], PdfObject dicPar) {
1669: if (dicPar == null || !dicPar.isDictionary())
1670: return in;
1671: PdfDictionary dic = (PdfDictionary) dicPar;
1672: PdfObject obj = getPdfObject(dic.get(PdfName.PREDICTOR));
1673: if (obj == null || !obj.isNumber())
1674: return in;
1675: int predictor = ((PdfNumber) obj).intValue();
1676: if (predictor < 10)
1677: return in;
1678: int width = 1;
1679: obj = getPdfObject(dic.get(PdfName.COLUMNS));
1680: if (obj != null && obj.isNumber())
1681: width = ((PdfNumber) obj).intValue();
1682: int colors = 1;
1683: obj = getPdfObject(dic.get(PdfName.COLORS));
1684: if (obj != null && obj.isNumber())
1685: colors = ((PdfNumber) obj).intValue();
1686: int bpc = 8;
1687: obj = getPdfObject(dic.get(PdfName.BITSPERCOMPONENT));
1688: if (obj != null && obj.isNumber())
1689: bpc = ((PdfNumber) obj).intValue();
1690: DataInputStream dataStream = new DataInputStream(
1691: new ByteArrayInputStream(in));
1692: ByteArrayOutputStream fout = new ByteArrayOutputStream(
1693: in.length);
1694: int bytesPerPixel = colors * bpc / 8;
1695: int bytesPerRow = (colors * width * bpc + 7) / 8;
1696: byte[] curr = new byte[bytesPerRow];
1697: byte[] prior = new byte[bytesPerRow];
1698:
1699: // Decode the (sub)image row-by-row
1700: while (true) {
1701: // Read the filter type byte and a row of data
1702: int filter = 0;
1703: try {
1704: filter = dataStream.read();
1705: if (filter < 0) {
1706: return fout.toByteArray();
1707: }
1708: dataStream.readFully(curr, 0, bytesPerRow);
1709: } catch (Exception e) {
1710: return fout.toByteArray();
1711: }
1712:
1713: switch (filter) {
1714: case 0: //PNG_FILTER_NONE
1715: break;
1716: case 1: //PNG_FILTER_SUB
1717: for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1718: curr[i] += curr[i - bytesPerPixel];
1719: }
1720: break;
1721: case 2: //PNG_FILTER_UP
1722: for (int i = 0; i < bytesPerRow; i++) {
1723: curr[i] += prior[i];
1724: }
1725: break;
1726: case 3: //PNG_FILTER_AVERAGE
1727: for (int i = 0; i < bytesPerPixel; i++) {
1728: curr[i] += prior[i] / 2;
1729: }
1730: for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1731: curr[i] += ((curr[i - bytesPerPixel] & 0xff) + (prior[i] & 0xff)) / 2;
1732: }
1733: break;
1734: case 4: //PNG_FILTER_PAETH
1735: for (int i = 0; i < bytesPerPixel; i++) {
1736: curr[i] += prior[i];
1737: }
1738:
1739: for (int i = bytesPerPixel; i < bytesPerRow; i++) {
1740: int a = curr[i - bytesPerPixel] & 0xff;
1741: int b = prior[i] & 0xff;
1742: int c = prior[i - bytesPerPixel] & 0xff;
1743:
1744: int p = a + b - c;
1745: int pa = Math.abs(p - a);
1746: int pb = Math.abs(p - b);
1747: int pc = Math.abs(p - c);
1748:
1749: int ret;
1750:
1751: if ((pa <= pb) && (pa <= pc)) {
1752: ret = a;
1753: } else if (pb <= pc) {
1754: ret = b;
1755: } else {
1756: ret = c;
1757: }
1758: curr[i] += (byte) (ret);
1759: }
1760: break;
1761: default:
1762: // Error -- uknown filter type
1763: throw new RuntimeException("PNG filter unknown.");
1764: }
1765: try {
1766: fout.write(curr);
1767: } catch (IOException ioe) {
1768: // Never happens
1769: }
1770:
1771: // Swap curr and prior
1772: byte[] tmp = prior;
1773: prior = curr;
1774: curr = tmp;
1775: }
1776: }
1777:
1778: /** A helper to FlateDecode.
1779: * @param in the input data
1780: * @param strict <CODE>true</CODE> to read a correct stream. <CODE>false</CODE>
1781: * to try to read a corrupted stream
1782: * @return the decoded data
1783: */
1784: public static byte[] FlateDecode(byte in[], boolean strict) {
1785: ByteArrayInputStream stream = new ByteArrayInputStream(in);
1786: InflaterInputStream zip = new InflaterInputStream(stream);
1787: ByteArrayOutputStream out = new ByteArrayOutputStream();
1788: byte b[] = new byte[strict ? 4092 : 1];
1789: try {
1790: int n;
1791: while ((n = zip.read(b)) >= 0) {
1792: out.write(b, 0, n);
1793: }
1794: zip.close();
1795: out.close();
1796: return out.toByteArray();
1797: } catch (Exception e) {
1798: if (strict)
1799: return null;
1800: return out.toByteArray();
1801: }
1802: }
1803:
1804: /** Decodes a stream that has the ASCIIHexDecode filter.
1805: * @param in the input data
1806: * @return the decoded data
1807: */
1808: public static byte[] ASCIIHexDecode(byte in[]) {
1809: ByteArrayOutputStream out = new ByteArrayOutputStream();
1810: boolean first = true;
1811: int n1 = 0;
1812: for (int k = 0; k < in.length; ++k) {
1813: int ch = in[k] & 0xff;
1814: if (ch == '>')
1815: break;
1816: if (PRTokeniser.isWhitespace(ch))
1817: continue;
1818: int n = PRTokeniser.getHex(ch);
1819: if (n == -1)
1820: throw new RuntimeException(
1821: "Illegal character in ASCIIHexDecode.");
1822: if (first)
1823: n1 = n;
1824: else
1825: out.write((byte) ((n1 << 4) + n));
1826: first = !first;
1827: }
1828: if (!first)
1829: out.write((byte) (n1 << 4));
1830: return out.toByteArray();
1831: }
1832:
1833: /** Decodes a stream that has the ASCII85Decode filter.
1834: * @param in the input data
1835: * @return the decoded data
1836: */
1837: public static byte[] ASCII85Decode(byte in[]) {
1838: ByteArrayOutputStream out = new ByteArrayOutputStream();
1839: int state = 0;
1840: int chn[] = new int[5];
1841: for (int k = 0; k < in.length; ++k) {
1842: int ch = in[k] & 0xff;
1843: if (ch == '~')
1844: break;
1845: if (PRTokeniser.isWhitespace(ch))
1846: continue;
1847: if (ch == 'z' && state == 0) {
1848: out.write(0);
1849: out.write(0);
1850: out.write(0);
1851: out.write(0);
1852: continue;
1853: }
1854: if (ch < '!' || ch > 'u')
1855: throw new RuntimeException(
1856: "Illegal character in ASCII85Decode.");
1857: chn[state] = ch - '!';
1858: ++state;
1859: if (state == 5) {
1860: state = 0;
1861: int r = 0;
1862: for (int j = 0; j < 5; ++j)
1863: r = r * 85 + chn[j];
1864: out.write((byte) (r >> 24));
1865: out.write((byte) (r >> 16));
1866: out.write((byte) (r >> 8));
1867: out.write((byte) r);
1868: }
1869: }
1870: int r = 0;
1871: if (state == 1)
1872: throw new RuntimeException(
1873: "Illegal length in ASCII85Decode.");
1874: if (state == 2) {
1875: r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + 85
1876: * 85 * 85 + 85 * 85 + 85;
1877: out.write((byte) (r >> 24));
1878: } else if (state == 3) {
1879: r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85
1880: + chn[2] * 85 * 85 + 85 * 85 + 85;
1881: out.write((byte) (r >> 24));
1882: out.write((byte) (r >> 16));
1883: } else if (state == 4) {
1884: r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85
1885: + chn[2] * 85 * 85 + chn[3] * 85 + 85;
1886: out.write((byte) (r >> 24));
1887: out.write((byte) (r >> 16));
1888: out.write((byte) (r >> 8));
1889: }
1890: return out.toByteArray();
1891: }
1892:
1893: /** Decodes a stream that has the LZWDecode filter.
1894: * @param in the input data
1895: * @return the decoded data
1896: */
1897: public static byte[] LZWDecode(byte in[]) {
1898: ByteArrayOutputStream out = new ByteArrayOutputStream();
1899: LZWDecoder lzw = new LZWDecoder();
1900: lzw.decode(in, out);
1901: return out.toByteArray();
1902: }
1903:
1904: /** Checks if the document had errors and was rebuilt.
1905: * @return true if rebuilt.
1906: *
1907: */
1908: public boolean isRebuilt() {
1909: return this .rebuilt;
1910: }
1911:
1912: /** Gets the dictionary that represents a page.
1913: * @param pageNum the page number. 1 is the first
1914: * @return the page dictionary
1915: */
1916: public PdfDictionary getPageN(int pageNum) {
1917: PdfDictionary dic = pageRefs.getPageN(pageNum);
1918: if (dic == null)
1919: return null;
1920: if (appendable)
1921: dic.setIndRef(pageRefs.getPageOrigRef(pageNum));
1922: return dic;
1923: }
1924:
1925: /**
1926: * @param pageNum
1927: * @return a Dictionary object
1928: */
1929: public PdfDictionary getPageNRelease(int pageNum) {
1930: PdfDictionary dic = getPageN(pageNum);
1931: pageRefs.releasePage(pageNum);
1932: return dic;
1933: }
1934:
1935: /**
1936: * @param pageNum
1937: */
1938: public void releasePage(int pageNum) {
1939: pageRefs.releasePage(pageNum);
1940: }
1941:
1942: /**
1943: *
1944: */
1945: public void resetReleasePage() {
1946: pageRefs.resetReleasePage();
1947: }
1948:
1949: /** Gets the page reference to this page.
1950: * @param pageNum the page number. 1 is the first
1951: * @return the page reference
1952: */
1953: public PRIndirectReference getPageOrigRef(int pageNum) {
1954: return pageRefs.getPageOrigRef(pageNum);
1955: }
1956:
1957: /** Gets the contents of the page.
1958: * @param pageNum the page number. 1 is the first
1959: * @param file the location of the PDF document
1960: * @throws IOException on error
1961: * @return the content
1962: */
1963: public byte[] getPageContent(int pageNum,
1964: RandomAccessFileOrArray file) throws IOException {
1965: PdfDictionary page = getPageNRelease(pageNum);
1966: if (page == null)
1967: return null;
1968: PdfObject contents = getPdfObjectRelease(page
1969: .get(PdfName.CONTENTS));
1970: if (contents == null)
1971: return new byte[0];
1972: ByteArrayOutputStream bout = null;
1973: if (contents.isStream()) {
1974: return getStreamBytes((PRStream) contents, file);
1975: } else if (contents.isArray()) {
1976: PdfArray array = (PdfArray) contents;
1977: ArrayList list = array.getArrayList();
1978: bout = new ByteArrayOutputStream();
1979: for (int k = 0; k < list.size(); ++k) {
1980: PdfObject item = getPdfObjectRelease((PdfObject) list
1981: .get(k));
1982: if (item == null || !item.isStream())
1983: continue;
1984: byte[] b = getStreamBytes((PRStream) item, file);
1985: bout.write(b);
1986: if (k != list.size() - 1)
1987: bout.write('\n');
1988: }
1989: return bout.toByteArray();
1990: } else
1991: return new byte[0];
1992: }
1993:
1994: /** Gets the contents of the page.
1995: * @param pageNum the page number. 1 is the first
1996: * @throws IOException on error
1997: * @return the content
1998: */
1999: public byte[] getPageContent(int pageNum) throws IOException {
2000: RandomAccessFileOrArray rf = getSafeFile();
2001: try {
2002: rf.reOpen();
2003: return getPageContent(pageNum, rf);
2004: } finally {
2005: try {
2006: rf.close();
2007: } catch (Exception e) {
2008: }
2009: }
2010: }
2011:
2012: protected void killXref(PdfObject obj) {
2013: if (obj == null)
2014: return;
2015: if ((obj instanceof PdfIndirectReference) && !obj.isIndirect())
2016: return;
2017: switch (obj.type()) {
2018: case PdfObject.INDIRECT: {
2019: int xr = ((PRIndirectReference) obj).getNumber();
2020: obj = (PdfObject) xrefObj.get(xr);
2021: xrefObj.set(xr, null);
2022: freeXref = xr;
2023: killXref(obj);
2024: break;
2025: }
2026: case PdfObject.ARRAY: {
2027: ArrayList t = ((PdfArray) obj).getArrayList();
2028: for (int i = 0; i < t.size(); ++i)
2029: killXref((PdfObject) t.get(i));
2030: break;
2031: }
2032: case PdfObject.STREAM:
2033: case PdfObject.DICTIONARY: {
2034: PdfDictionary dic = (PdfDictionary) obj;
2035: for (Iterator i = dic.getKeys().iterator(); i.hasNext();) {
2036: killXref(dic.get((PdfName) i.next()));
2037: }
2038: break;
2039: }
2040: }
2041: }
2042:
2043: /** Sets the contents of the page.
2044: * @param content the new page content
2045: * @param pageNum the page number. 1 is the first
2046: */
2047: public void setPageContent(int pageNum, byte content[]) {
2048: PdfDictionary page = getPageN(pageNum);
2049: if (page == null)
2050: return;
2051: PdfObject contents = page.get(PdfName.CONTENTS);
2052: freeXref = -1;
2053: killXref(contents);
2054: if (freeXref == -1) {
2055: xrefObj.add(null);
2056: freeXref = xrefObj.size() - 1;
2057: }
2058: page.put(PdfName.CONTENTS, new PRIndirectReference(this ,
2059: freeXref));
2060: xrefObj.set(freeXref, new PRStream(this , content));
2061: }
2062:
2063: /** Get the content from a stream applying the required filters.
2064: * @param stream the stream
2065: * @param file the location where the stream is
2066: * @throws IOException on error
2067: * @return the stream content
2068: */
2069: public static byte[] getStreamBytes(PRStream stream,
2070: RandomAccessFileOrArray file) throws IOException {
2071: PdfObject filter = getPdfObjectRelease(stream
2072: .get(PdfName.FILTER));
2073: byte[] b = getStreamBytesRaw(stream, file);
2074: ArrayList filters = new ArrayList();
2075: if (filter != null) {
2076: if (filter.isName())
2077: filters.add(filter);
2078: else if (filter.isArray())
2079: filters = ((PdfArray) filter).getArrayList();
2080: }
2081: ArrayList dp = new ArrayList();
2082: PdfObject dpo = getPdfObjectRelease(stream
2083: .get(PdfName.DECODEPARMS));
2084: if (dpo == null || (!dpo.isDictionary() && !dpo.isArray()))
2085: dpo = getPdfObjectRelease(stream.get(PdfName.DP));
2086: if (dpo != null) {
2087: if (dpo.isDictionary())
2088: dp.add(dpo);
2089: else if (dpo.isArray())
2090: dp = ((PdfArray) dpo).getArrayList();
2091: }
2092: String name;
2093: for (int j = 0; j < filters.size(); ++j) {
2094: name = ((PdfName) getPdfObjectRelease((PdfObject) filters
2095: .get(j))).toString();
2096: if (name.equals("/FlateDecode") || name.equals("/Fl")) {
2097: b = FlateDecode(b);
2098: PdfObject dicParam = null;
2099: if (j < dp.size()) {
2100: dicParam = (PdfObject) dp.get(j);
2101: b = decodePredictor(b, dicParam);
2102: }
2103: } else if (name.equals("/ASCIIHexDecode")
2104: || name.equals("/AHx"))
2105: b = ASCIIHexDecode(b);
2106: else if (name.equals("/ASCII85Decode")
2107: || name.equals("/A85"))
2108: b = ASCII85Decode(b);
2109: else if (name.equals("/LZWDecode")) {
2110: b = LZWDecode(b);
2111: PdfObject dicParam = null;
2112: if (j < dp.size()) {
2113: dicParam = (PdfObject) dp.get(j);
2114: b = decodePredictor(b, dicParam);
2115: }
2116: } else if (name.equals("/Crypt")) {
2117: } else
2118: throw new IOException("The filter " + name
2119: + " is not supported.");
2120: }
2121: return b;
2122: }
2123:
2124: /** Get the content from a stream applying the required filters.
2125: * @param stream the stream
2126: * @throws IOException on error
2127: * @return the stream content
2128: */
2129: public static byte[] getStreamBytes(PRStream stream)
2130: throws IOException {
2131: RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
2132: try {
2133: rf.reOpen();
2134: return getStreamBytes(stream, rf);
2135: } finally {
2136: try {
2137: rf.close();
2138: } catch (Exception e) {
2139: }
2140: }
2141: }
2142:
2143: /** Get the content from a stream as it is without applying any filter.
2144: * @param stream the stream
2145: * @param file the location where the stream is
2146: * @throws IOException on error
2147: * @return the stream content
2148: */
2149: public static byte[] getStreamBytesRaw(PRStream stream,
2150: RandomAccessFileOrArray file) throws IOException {
2151: PdfReader reader = stream.getReader();
2152: byte b[];
2153: if (stream.getOffset() < 0)
2154: b = stream.getBytes();
2155: else {
2156: b = new byte[stream.getLength()];
2157: file.seek(stream.getOffset());
2158: file.readFully(b);
2159: PdfEncryption decrypt = reader.getDecrypt();
2160: if (decrypt != null) {
2161: PdfObject filter = getPdfObjectRelease(stream
2162: .get(PdfName.FILTER));
2163: ArrayList filters = new ArrayList();
2164: if (filter != null) {
2165: if (filter.isName())
2166: filters.add(filter);
2167: else if (filter.isArray())
2168: filters = ((PdfArray) filter).getArrayList();
2169: }
2170: boolean skip = false;
2171: for (int k = 0; k < filters.size(); ++k) {
2172: PdfObject obj = getPdfObjectRelease((PdfObject) filters
2173: .get(k));
2174: if (obj != null && obj.toString().equals("/Crypt")) {
2175: skip = true;
2176: break;
2177: }
2178: }
2179: if (!skip) {
2180: decrypt.setHashKey(stream.getObjNum(), stream
2181: .getObjGen());
2182: b = decrypt.decryptByteArray(b);
2183: }
2184: }
2185: }
2186: return b;
2187: }
2188:
2189: /** Get the content from a stream as it is without applying any filter.
2190: * @param stream the stream
2191: * @throws IOException on error
2192: * @return the stream content
2193: */
2194: public static byte[] getStreamBytesRaw(PRStream stream)
2195: throws IOException {
2196: RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
2197: try {
2198: rf.reOpen();
2199: return getStreamBytesRaw(stream, rf);
2200: } finally {
2201: try {
2202: rf.close();
2203: } catch (Exception e) {
2204: }
2205: }
2206: }
2207:
2208: /** Eliminates shared streams if they exist. */
2209: public void eliminateSharedStreams() {
2210: if (!sharedStreams)
2211: return;
2212: sharedStreams = false;
2213: if (pageRefs.size() == 1)
2214: return;
2215: ArrayList newRefs = new ArrayList();
2216: ArrayList newStreams = new ArrayList();
2217: IntHashtable visited = new IntHashtable();
2218: for (int k = 1; k <= pageRefs.size(); ++k) {
2219: PdfDictionary page = pageRefs.getPageN(k);
2220: if (page == null)
2221: continue;
2222: PdfObject contents = getPdfObject(page
2223: .get(PdfName.CONTENTS));
2224: if (contents == null)
2225: continue;
2226: if (contents.isStream()) {
2227: PRIndirectReference ref = (PRIndirectReference) page
2228: .get(PdfName.CONTENTS);
2229: if (visited.containsKey(ref.getNumber())) {
2230: // need to duplicate
2231: newRefs.add(ref);
2232: newStreams.add(new PRStream((PRStream) contents,
2233: null));
2234: } else
2235: visited.put(ref.getNumber(), 1);
2236: } else if (contents.isArray()) {
2237: PdfArray array = (PdfArray) contents;
2238: ArrayList list = array.getArrayList();
2239: for (int j = 0; j < list.size(); ++j) {
2240: PRIndirectReference ref = (PRIndirectReference) list
2241: .get(j);
2242: if (visited.containsKey(ref.getNumber())) {
2243: // need to duplicate
2244: newRefs.add(ref);
2245: newStreams.add(new PRStream(
2246: (PRStream) getPdfObject(ref), null));
2247: } else
2248: visited.put(ref.getNumber(), 1);
2249: }
2250: }
2251: }
2252: if (newStreams.isEmpty())
2253: return;
2254: for (int k = 0; k < newStreams.size(); ++k) {
2255: xrefObj.add(newStreams.get(k));
2256: PRIndirectReference ref = (PRIndirectReference) newRefs
2257: .get(k);
2258: ref.setNumber(xrefObj.size() - 1, 0);
2259: }
2260: }
2261:
2262: /** Checks if the document was changed.
2263: * @return <CODE>true</CODE> if the document was changed,
2264: * <CODE>false</CODE> otherwise
2265: */
2266: public boolean isTampered() {
2267: return tampered;
2268: }
2269:
2270: /**
2271: * Sets the tampered state. A tampered PdfReader cannot be reused in PdfStamper.
2272: * @param tampered the tampered state
2273: */
2274: public void setTampered(boolean tampered) {
2275: this .tampered = tampered;
2276: }
2277:
2278: /** Gets the XML metadata.
2279: * @throws IOException on error
2280: * @return the XML metadata
2281: */
2282: public byte[] getMetadata() throws IOException {
2283: PdfObject obj = getPdfObject(catalog.get(PdfName.METADATA));
2284: if (!(obj instanceof PRStream))
2285: return null;
2286: RandomAccessFileOrArray rf = getSafeFile();
2287: byte b[] = null;
2288: try {
2289: rf.reOpen();
2290: b = getStreamBytes((PRStream) obj, rf);
2291: } finally {
2292: try {
2293: rf.close();
2294: } catch (Exception e) {
2295: // empty on purpose
2296: }
2297: }
2298: return b;
2299: }
2300:
2301: /**
2302: * Gets the byte address of the last xref table.
2303: * @return the byte address of the last xref table
2304: */
2305: public int getLastXref() {
2306: return lastXref;
2307: }
2308:
2309: /**
2310: * Gets the number of xref objects.
2311: * @return the number of xref objects
2312: */
2313: public int getXrefSize() {
2314: return xrefObj.size();
2315: }
2316:
2317: /**
2318: * Gets the byte address of the %%EOF marker.
2319: * @return the byte address of the %%EOF marker
2320: */
2321: public int getEofPos() {
2322: return eofPos;
2323: }
2324:
2325: /**
2326: * Gets the PDF version. Only the last version char is returned. For example
2327: * version 1.4 is returned as '4'.
2328: * @return the PDF version
2329: */
2330: public char getPdfVersion() {
2331: return pdfVersion;
2332: }
2333:
2334: /**
2335: * Returns <CODE>true</CODE> if the PDF is encrypted.
2336: * @return <CODE>true</CODE> if the PDF is encrypted
2337: */
2338: public boolean isEncrypted() {
2339: return encrypted;
2340: }
2341:
2342: /**
2343: * Gets the encryption permissions. It can be used directly in
2344: * <CODE>PdfWriter.setEncryption()</CODE>.
2345: * @return the encryption permissions
2346: */
2347: public int getPermissions() {
2348: return pValue;
2349: }
2350:
2351: /**
2352: * Returns <CODE>true</CODE> if the PDF has a 128 bit key encryption.
2353: * @return <CODE>true</CODE> if the PDF has a 128 bit key encryption
2354: */
2355: public boolean is128Key() {
2356: return rValue == 3;
2357: }
2358:
2359: /**
2360: * Gets the trailer dictionary
2361: * @return the trailer dictionary
2362: */
2363: public PdfDictionary getTrailer() {
2364: return trailer;
2365: }
2366:
2367: PdfEncryption getDecrypt() {
2368: return decrypt;
2369: }
2370:
2371: static boolean equalsn(byte a1[], byte a2[]) {
2372: int length = a2.length;
2373: for (int k = 0; k < length; ++k) {
2374: if (a1[k] != a2[k])
2375: return false;
2376: }
2377: return true;
2378: }
2379:
2380: static boolean existsName(PdfDictionary dic, PdfName key,
2381: PdfName value) {
2382: PdfObject type = getPdfObjectRelease(dic.get(key));
2383: if (type == null || !type.isName())
2384: return false;
2385: PdfName name = (PdfName) type;
2386: return name.equals(value);
2387: }
2388:
2389: static String getFontName(PdfDictionary dic) {
2390: if (dic == null)
2391: return null;
2392: PdfObject type = getPdfObjectRelease(dic.get(PdfName.BASEFONT));
2393: if (type == null || !type.isName())
2394: return null;
2395: return PdfName.decodeName(type.toString());
2396: }
2397:
2398: static String getSubsetPrefix(PdfDictionary dic) {
2399: if (dic == null)
2400: return null;
2401: String s = getFontName(dic);
2402: if (s == null)
2403: return null;
2404: if (s.length() < 8 || s.charAt(6) != '+')
2405: return null;
2406: for (int k = 0; k < 6; ++k) {
2407: char c = s.charAt(k);
2408: if (c < 'A' || c > 'Z')
2409: return null;
2410: }
2411: return s;
2412: }
2413:
2414: /** Finds all the font subsets and changes the prefixes to some
2415: * random values.
2416: * @return the number of font subsets altered
2417: */
2418: public int shuffleSubsetNames() {
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,
2430: PdfName.TRUETYPE)) {
2431: String s = getSubsetPrefix(dic);
2432: if (s == null)
2433: continue;
2434: String ns = BaseFont.createSubsetPrefix()
2435: + s.substring(7);
2436: PdfName newName = new PdfName(ns);
2437: dic.put(PdfName.BASEFONT, newName);
2438: setXrefPartialObject(k, dic);
2439: ++total;
2440: PdfDictionary fd = (PdfDictionary) getPdfObject(dic
2441: .get(PdfName.FONTDESCRIPTOR));
2442: if (fd == null)
2443: continue;
2444: fd.put(PdfName.FONTNAME, newName);
2445: } else if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE0)) {
2446: String s = getSubsetPrefix(dic);
2447: PdfArray arr = (PdfArray) getPdfObject(dic
2448: .get(PdfName.DESCENDANTFONTS));
2449: if (arr == null)
2450: continue;
2451: ArrayList list = arr.getArrayList();
2452: if (list.isEmpty())
2453: continue;
2454: PdfDictionary desc = (PdfDictionary) getPdfObject((PdfObject) list
2455: .get(0));
2456: String sde = getSubsetPrefix(desc);
2457: if (sde == null)
2458: continue;
2459: String ns = BaseFont.createSubsetPrefix();
2460: if (s != null)
2461: dic.put(PdfName.BASEFONT, new PdfName(ns
2462: + s.substring(7)));
2463: setXrefPartialObject(k, dic);
2464: PdfName newName = new PdfName(ns + sde.substring(7));
2465: desc.put(PdfName.BASEFONT, newName);
2466: ++total;
2467: PdfDictionary fd = (PdfDictionary) getPdfObject(desc
2468: .get(PdfName.FONTDESCRIPTOR));
2469: if (fd == null)
2470: continue;
2471: fd.put(PdfName.FONTNAME, newName);
2472: }
2473: }
2474: return total;
2475: }
2476:
2477: /** Finds all the fonts not subset but embedded and marks them as subset.
2478: * @return the number of fonts altered
2479: */
2480: public int createFakeFontSubsets() {
2481: int total = 0;
2482: for (int k = 1; k < xrefObj.size(); ++k) {
2483: PdfObject obj = getPdfObjectRelease(k);
2484: if (obj == null || !obj.isDictionary())
2485: continue;
2486: PdfDictionary dic = (PdfDictionary) obj;
2487: if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
2488: continue;
2489: if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
2490: || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
2491: || existsName(dic, PdfName.SUBTYPE,
2492: PdfName.TRUETYPE)) {
2493: String s = getSubsetPrefix(dic);
2494: if (s != null)
2495: continue;
2496: s = getFontName(dic);
2497: if (s == null)
2498: continue;
2499: String ns = BaseFont.createSubsetPrefix() + s;
2500: PdfDictionary fd = (PdfDictionary) getPdfObjectRelease(dic
2501: .get(PdfName.FONTDESCRIPTOR));
2502: if (fd == null)
2503: continue;
2504: if (fd.get(PdfName.FONTFILE) == null
2505: && fd.get(PdfName.FONTFILE2) == null
2506: && fd.get(PdfName.FONTFILE3) == null)
2507: continue;
2508: fd = (PdfDictionary) getPdfObject(dic
2509: .get(PdfName.FONTDESCRIPTOR));
2510: PdfName newName = new PdfName(ns);
2511: dic.put(PdfName.BASEFONT, newName);
2512: fd.put(PdfName.FONTNAME, newName);
2513: setXrefPartialObject(k, dic);
2514: ++total;
2515: }
2516: }
2517: return total;
2518: }
2519:
2520: private static PdfArray getNameArray(PdfObject obj) {
2521: if (obj == null)
2522: return null;
2523: obj = getPdfObjectRelease(obj);
2524: if (obj == null)
2525: return null;
2526: if (obj.isArray())
2527: return (PdfArray) obj;
2528: else if (obj.isDictionary()) {
2529: PdfObject arr2 = getPdfObjectRelease(((PdfDictionary) obj)
2530: .get(PdfName.D));
2531: if (arr2 != null && arr2.isArray())
2532: return (PdfArray) arr2;
2533: }
2534: return null;
2535: }
2536:
2537: /**
2538: * Gets all the named destinations as an <CODE>HashMap</CODE>. The key is the name
2539: * and the value is the destinations array.
2540: * @return gets all the named destinations
2541: */
2542: public HashMap getNamedDestination() {
2543: HashMap names = getNamedDestinationFromNames();
2544: names.putAll(getNamedDestinationFromStrings());
2545: return names;
2546: }
2547:
2548: /**
2549: * Gets the named destinations from the /Dests key in the catalog as an <CODE>HashMap</CODE>. The key is the name
2550: * and the value is the destinations array.
2551: * @return gets the named destinations
2552: */
2553: public HashMap getNamedDestinationFromNames() {
2554: HashMap names = new HashMap();
2555: if (catalog.get(PdfName.DESTS) != null) {
2556: PdfDictionary dic = (PdfDictionary) getPdfObjectRelease(catalog
2557: .get(PdfName.DESTS));
2558: if (dic == null)
2559: return names;
2560: Set keys = dic.getKeys();
2561: for (Iterator it = keys.iterator(); it.hasNext();) {
2562: PdfName key = (PdfName) it.next();
2563: String name = PdfName.decodeName(key.toString());
2564: PdfArray arr = getNameArray(dic.get(key));
2565: if (arr != null)
2566: names.put(name, arr);
2567: }
2568: }
2569: return names;
2570: }
2571:
2572: /**
2573: * Gets the named destinations from the /Names key in the catalog as an <CODE>HashMap</CODE>. The key is the name
2574: * and the value is the destinations array.
2575: * @return gets the named destinations
2576: */
2577: public HashMap getNamedDestinationFromStrings() {
2578: if (catalog.get(PdfName.NAMES) != null) {
2579: PdfDictionary dic = (PdfDictionary) getPdfObjectRelease(catalog
2580: .get(PdfName.NAMES));
2581: if (dic != null) {
2582: dic = (PdfDictionary) getPdfObjectRelease(dic
2583: .get(PdfName.DESTS));
2584: if (dic != null) {
2585: HashMap names = PdfNameTree.readTree(dic);
2586: for (Iterator it = names.entrySet().iterator(); it
2587: .hasNext();) {
2588: Map.Entry entry = (Map.Entry) it.next();
2589: PdfArray arr = getNameArray((PdfObject) entry
2590: .getValue());
2591: if (arr != null)
2592: entry.setValue(arr);
2593: else
2594: it.remove();
2595: }
2596: return names;
2597: }
2598: }
2599: }
2600: return new HashMap();
2601: }
2602:
2603: private boolean replaceNamedDestination(PdfObject obj, HashMap names) {
2604: obj = getPdfObject(obj);
2605: int objIdx = lastXrefPartial;
2606: releaseLastXrefPartial();
2607: if (obj != null && obj.isDictionary()) {
2608: PdfObject ob2 = getPdfObjectRelease(((PdfDictionary) obj)
2609: .get(PdfName.DEST));
2610: String name = null;
2611: if (ob2 != null) {
2612: if (ob2.isName())
2613: name = PdfName.decodeName(ob2.toString());
2614: else if (ob2.isString())
2615: name = ob2.toString();
2616: PdfArray dest = (PdfArray) names.get(name);
2617: if (dest != null) {
2618: ((PdfDictionary) obj).put(PdfName.DEST, dest);
2619: setXrefPartialObject(objIdx, obj);
2620: return true;
2621: }
2622: } else if ((ob2 = getPdfObject(((PdfDictionary) obj)
2623: .get(PdfName.A))) != null) {
2624: int obj2Idx = lastXrefPartial;
2625: releaseLastXrefPartial();
2626: PdfDictionary dic = (PdfDictionary) ob2;
2627: PdfName type = (PdfName) getPdfObjectRelease(dic
2628: .get(PdfName.S));
2629: if (PdfName.GOTO.equals(type)) {
2630: PdfObject ob3 = getPdfObjectRelease(dic
2631: .get(PdfName.D));
2632: if (ob3 != null) {
2633: if (ob3.isName())
2634: name = PdfName.decodeName(ob3.toString());
2635: else if (ob3.isString())
2636: name = ob3.toString();
2637: }
2638: PdfArray dest = (PdfArray) names.get(name);
2639: if (dest != null) {
2640: dic.put(PdfName.D, dest);
2641: setXrefPartialObject(obj2Idx, ob2);
2642: setXrefPartialObject(objIdx, obj);
2643: return true;
2644: }
2645: }
2646: }
2647: }
2648: return false;
2649: }
2650:
2651: /**
2652: * Removes all the fields from the document.
2653: */
2654: public void removeFields() {
2655: pageRefs.resetReleasePage();
2656: for (int k = 1; k <= pageRefs.size(); ++k) {
2657: PdfDictionary page = pageRefs.getPageN(k);
2658: PdfArray annots = (PdfArray) getPdfObject(page
2659: .get(PdfName.ANNOTS));
2660: if (annots == null) {
2661: pageRefs.releasePage(k);
2662: continue;
2663: }
2664: ArrayList arr = annots.getArrayList();
2665: for (int j = 0; j < arr.size(); ++j) {
2666: PdfDictionary annot = (PdfDictionary) getPdfObjectRelease((PdfObject) arr
2667: .get(j));
2668: if (PdfName.WIDGET.equals(annot.get(PdfName.SUBTYPE)))
2669: arr.remove(j--);
2670: }
2671: if (arr.isEmpty())
2672: page.remove(PdfName.ANNOTS);
2673: else
2674: pageRefs.releasePage(k);
2675: }
2676: catalog.remove(PdfName.ACROFORM);
2677: pageRefs.resetReleasePage();
2678: }
2679:
2680: /**
2681: * Removes all the annotations and fields from the document.
2682: */
2683: public void removeAnnotations() {
2684: pageRefs.resetReleasePage();
2685: for (int k = 1; k <= pageRefs.size(); ++k) {
2686: PdfDictionary page = pageRefs.getPageN(k);
2687: if (page.get(PdfName.ANNOTS) == null)
2688: pageRefs.releasePage(k);
2689: else
2690: page.remove(PdfName.ANNOTS);
2691: }
2692: catalog.remove(PdfName.ACROFORM);
2693: pageRefs.resetReleasePage();
2694: }
2695:
2696: public ArrayList getLinks(int page) {
2697: pageRefs.resetReleasePage();
2698: ArrayList result = new ArrayList();
2699: PdfDictionary pageDic = pageRefs.getPageN(page);
2700: if (pageDic.get(PdfName.ANNOTS) != null) {
2701: PdfArray annots = (PdfArray) getPdfObject(pageDic
2702: .get(PdfName.ANNOTS));
2703: ArrayList arr = annots.getArrayList();
2704: for (int j = 0; j < arr.size(); ++j) {
2705: PdfDictionary annot = (PdfDictionary) getPdfObjectRelease((PdfObject) arr
2706: .get(j));
2707:
2708: if (PdfName.LINK.equals(annot.get(PdfName.SUBTYPE))) {
2709: result
2710: .add(new PdfAnnotation.PdfImportedLink(
2711: annot));
2712: }
2713: }
2714: }
2715: pageRefs.releasePage(page);
2716: pageRefs.resetReleasePage();
2717: return result;
2718: }
2719:
2720: private void iterateBookmarks(PdfObject outlineRef, HashMap names) {
2721: while (outlineRef != null) {
2722: replaceNamedDestination(outlineRef, names);
2723: PdfDictionary outline = (PdfDictionary) getPdfObjectRelease(outlineRef);
2724: PdfObject first = outline.get(PdfName.FIRST);
2725: if (first != null) {
2726: iterateBookmarks(first, names);
2727: }
2728: outlineRef = outline.get(PdfName.NEXT);
2729: }
2730: }
2731:
2732: /** Replaces all the local named links with the actual destinations. */
2733: public void consolidateNamedDestinations() {
2734: if (consolidateNamedDestinations)
2735: return;
2736: consolidateNamedDestinations = true;
2737: HashMap names = getNamedDestination();
2738: if (names.isEmpty())
2739: return;
2740: for (int k = 1; k <= pageRefs.size(); ++k) {
2741: PdfDictionary page = pageRefs.getPageN(k);
2742: PdfObject annotsRef;
2743: PdfArray annots = (PdfArray) getPdfObject(annotsRef = page
2744: .get(PdfName.ANNOTS));
2745: int annotIdx = lastXrefPartial;
2746: releaseLastXrefPartial();
2747: if (annots == null) {
2748: pageRefs.releasePage(k);
2749: continue;
2750: }
2751: ArrayList list = annots.getArrayList();
2752: boolean commitAnnots = false;
2753: for (int an = 0; an < list.size(); ++an) {
2754: PdfObject objRef = (PdfObject) list.get(an);
2755: if (replaceNamedDestination(objRef, names)
2756: && !objRef.isIndirect())
2757: commitAnnots = true;
2758: }
2759: if (commitAnnots)
2760: setXrefPartialObject(annotIdx, annots);
2761: if (!commitAnnots || annotsRef.isIndirect())
2762: pageRefs.releasePage(k);
2763: }
2764: PdfDictionary outlines = (PdfDictionary) getPdfObjectRelease(catalog
2765: .get(PdfName.OUTLINES));
2766: if (outlines == null)
2767: return;
2768: iterateBookmarks(outlines.get(PdfName.FIRST), names);
2769: }
2770:
2771: protected static PdfDictionary duplicatePdfDictionary(
2772: PdfDictionary original, PdfDictionary copy,
2773: PdfReader newReader) {
2774: if (copy == null)
2775: copy = new PdfDictionary();
2776: for (Iterator it = original.getKeys().iterator(); it.hasNext();) {
2777: PdfName key = (PdfName) it.next();
2778: copy.put(key, duplicatePdfObject(original.get(key),
2779: newReader));
2780: }
2781: return copy;
2782: }
2783:
2784: protected static PdfObject duplicatePdfObject(PdfObject original,
2785: PdfReader newReader) {
2786: if (original == null)
2787: return null;
2788: switch (original.type()) {
2789: case PdfObject.DICTIONARY: {
2790: return duplicatePdfDictionary((PdfDictionary) original,
2791: null, newReader);
2792: }
2793: case PdfObject.STREAM: {
2794: PRStream org = (PRStream) original;
2795: PRStream stream = new PRStream(org, null, newReader);
2796: duplicatePdfDictionary(org, stream, newReader);
2797: return stream;
2798: }
2799: case PdfObject.ARRAY: {
2800: ArrayList list = ((PdfArray) original).getArrayList();
2801: PdfArray arr = new PdfArray();
2802: for (Iterator it = list.iterator(); it.hasNext();) {
2803: arr.add(duplicatePdfObject((PdfObject) it.next(),
2804: newReader));
2805: }
2806: return arr;
2807: }
2808: case PdfObject.INDIRECT: {
2809: PRIndirectReference org = (PRIndirectReference) original;
2810: return new PRIndirectReference(newReader, org.getNumber(),
2811: org.getGeneration());
2812: }
2813: default:
2814: return original;
2815: }
2816: }
2817:
2818: /**
2819: * Closes the reader
2820: */
2821: public void close() {
2822: if (!partial)
2823: return;
2824: try {
2825: tokens.close();
2826: } catch (IOException e) {
2827: throw new ExceptionConverter(e);
2828: }
2829: }
2830:
2831: protected void removeUnusedNode(PdfObject obj, boolean hits[]) {
2832: Stack state = new Stack();
2833: state.push(obj);
2834: while (!state.empty()) {
2835: Object current = state.pop();
2836: if (current == null)
2837: continue;
2838: ArrayList ar = null;
2839: PdfDictionary dic = null;
2840: PdfName[] keys = null;
2841: Object[] objs = null;
2842: int idx = 0;
2843: if (current instanceof PdfObject) {
2844: obj = (PdfObject) current;
2845: switch (obj.type()) {
2846: case PdfObject.DICTIONARY:
2847: case PdfObject.STREAM:
2848: dic = (PdfDictionary) obj;
2849: keys = new PdfName[dic.size()];
2850: dic.getKeys().toArray(keys);
2851: break;
2852: case PdfObject.ARRAY:
2853: ar = ((PdfArray) obj).getArrayList();
2854: break;
2855: case PdfObject.INDIRECT:
2856: PRIndirectReference ref = (PRIndirectReference) obj;
2857: int num = ref.getNumber();
2858: if (!hits[num]) {
2859: hits[num] = true;
2860: state.push(getPdfObjectRelease(ref));
2861: }
2862: continue;
2863: default:
2864: continue;
2865: }
2866: } else {
2867: objs = (Object[]) current;
2868: if (objs[0] instanceof ArrayList) {
2869: ar = (ArrayList) objs[0];
2870: idx = ((Integer) objs[1]).intValue();
2871: } else {
2872: keys = (PdfName[]) objs[0];
2873: dic = (PdfDictionary) objs[1];
2874: idx = ((Integer) objs[2]).intValue();
2875: }
2876: }
2877: if (ar != null) {
2878: for (int k = idx; k < ar.size(); ++k) {
2879: PdfObject v = (PdfObject) ar.get(k);
2880: if (v.isIndirect()) {
2881: int num = ((PRIndirectReference) v).getNumber();
2882: if (num >= xrefObj.size()
2883: || (!partial && xrefObj.get(num) == null)) {
2884: ar.set(k, PdfNull.PDFNULL);
2885: continue;
2886: }
2887: }
2888: if (objs == null)
2889: state.push(new Object[] { ar,
2890: new Integer(k + 1) });
2891: else {
2892: objs[1] = new Integer(k + 1);
2893: state.push(objs);
2894: }
2895: state.push(v);
2896: break;
2897: }
2898: } else {
2899: for (int k = idx; k < keys.length; ++k) {
2900: PdfName key = keys[k];
2901: PdfObject v = dic.get(key);
2902: if (v.isIndirect()) {
2903: int num = ((PRIndirectReference) v).getNumber();
2904: if (num >= xrefObj.size()
2905: || (!partial && xrefObj.get(num) == null)) {
2906: dic.put(key, PdfNull.PDFNULL);
2907: continue;
2908: }
2909: }
2910: if (objs == null)
2911: state.push(new Object[] { keys, dic,
2912: new Integer(k + 1) });
2913: else {
2914: objs[2] = new Integer(k + 1);
2915: state.push(objs);
2916: }
2917: state.push(v);
2918: break;
2919: }
2920: }
2921: }
2922: }
2923:
2924: /** Removes all the unreachable objects.
2925: * @return the number of indirect objects removed
2926: */
2927: public int removeUnusedObjects() {
2928: boolean hits[] = new boolean[xrefObj.size()];
2929: removeUnusedNode(trailer, hits);
2930: int total = 0;
2931: if (partial) {
2932: for (int k = 1; k < hits.length; ++k) {
2933: if (!hits[k]) {
2934: xref[k * 2] = -1;
2935: xref[k * 2 + 1] = 0;
2936: xrefObj.set(k, null);
2937: ++total;
2938: }
2939: }
2940: } else {
2941: for (int k = 1; k < hits.length; ++k) {
2942: if (!hits[k]) {
2943: xrefObj.set(k, null);
2944: ++total;
2945: }
2946: }
2947: }
2948: return total;
2949: }
2950:
2951: /** Gets a read-only version of <CODE>AcroFields</CODE>.
2952: * @return a read-only version of <CODE>AcroFields</CODE>
2953: */
2954: public AcroFields getAcroFields() {
2955: return new AcroFields(this , null);
2956: }
2957:
2958: /**
2959: * Gets the global document JavaScript.
2960: * @param file the document file
2961: * @throws IOException on error
2962: * @return the global document JavaScript
2963: */
2964: public String getJavaScript(RandomAccessFileOrArray file)
2965: throws IOException {
2966: PdfDictionary names = (PdfDictionary) getPdfObjectRelease(catalog
2967: .get(PdfName.NAMES));
2968: if (names == null)
2969: return null;
2970: PdfDictionary js = (PdfDictionary) getPdfObjectRelease(names
2971: .get(PdfName.JAVASCRIPT));
2972: if (js == null)
2973: return null;
2974: HashMap jscript = PdfNameTree.readTree(js);
2975: String sortedNames[] = new String[jscript.size()];
2976: sortedNames = (String[]) jscript.keySet().toArray(sortedNames);
2977: Arrays.sort(sortedNames);
2978: StringBuffer buf = new StringBuffer();
2979: for (int k = 0; k < sortedNames.length; ++k) {
2980: PdfDictionary j = (PdfDictionary) getPdfObjectRelease((PdfIndirectReference) jscript
2981: .get(sortedNames[k]));
2982: if (j == null)
2983: continue;
2984: PdfObject obj = getPdfObjectRelease(j.get(PdfName.JS));
2985: if (obj != null) {
2986: if (obj.isString())
2987: buf.append(((PdfString) obj).toUnicodeString())
2988: .append('\n');
2989: else if (obj.isStream()) {
2990: byte bytes[] = getStreamBytes((PRStream) obj, file);
2991: if (bytes.length >= 2 && bytes[0] == (byte) 254
2992: && bytes[1] == (byte) 255)
2993: buf.append(PdfEncodings.convertToString(bytes,
2994: PdfObject.TEXT_UNICODE));
2995: else
2996: buf.append(PdfEncodings.convertToString(bytes,
2997: PdfObject.TEXT_PDFDOCENCODING));
2998: buf.append('\n');
2999: }
3000: }
3001: }
3002: return buf.toString();
3003: }
3004:
3005: /**
3006: * Gets the global document JavaScript.
3007: * @throws IOException on error
3008: * @return the global document JavaScript
3009: */
3010: public String getJavaScript() throws IOException {
3011: RandomAccessFileOrArray rf = getSafeFile();
3012: try {
3013: rf.reOpen();
3014: return getJavaScript(rf);
3015: } finally {
3016: try {
3017: rf.close();
3018: } catch (Exception e) {
3019: }
3020: }
3021: }
3022:
3023: /**
3024: * Selects the pages to keep in the document. The pages are described as
3025: * ranges. The page ordering can be changed but
3026: * no page repetitions are allowed. Note that it may be very slow in partial mode.
3027: * @param ranges the comma separated ranges as described in {@link SequenceList}
3028: */
3029: public void selectPages(String ranges) {
3030: selectPages(SequenceList.expand(ranges, getNumberOfPages()));
3031: }
3032:
3033: /**
3034: * Selects the pages to keep in the document. The pages are described as a
3035: * <CODE>List</CODE> of <CODE>Integer</CODE>. The page ordering can be changed but
3036: * no page repetitions are allowed. Note that it may be very slow in partial mode.
3037: * @param pagesToKeep the pages to keep in the document
3038: */
3039: public void selectPages(List pagesToKeep) {
3040: pageRefs.selectPages(pagesToKeep);
3041: removeUnusedObjects();
3042: }
3043:
3044: /** Sets the viewer preferences as the sum of several constants.
3045: * @param preferences the viewer preferences
3046: * @see PdfViewerPreferences#setViewerPreferences
3047: */
3048: public void setViewerPreferences(int preferences) {
3049: this .viewerPreferences.setViewerPreferences(preferences);
3050: setViewerPreferences(this .viewerPreferences);
3051: }
3052:
3053: /** Adds a viewer preference
3054: * @param key a key for a viewer preference
3055: * @param value a value for the viewer preference
3056: * @see PdfViewerPreferences#addViewerPreference
3057: */
3058:
3059: public void addViewerPreference(PdfName key, PdfObject value) {
3060: this .viewerPreferences.addViewerPreference(key, value);
3061: setViewerPreferences(this .viewerPreferences);
3062: }
3063:
3064: void setViewerPreferences(PdfViewerPreferencesImp vp) {
3065: vp.addToCatalog(catalog);
3066: }
3067:
3068: /**
3069: * @return an int that contains the Viewer Preferences.
3070: */
3071: public int getSimpleViewerPreferences() {
3072: return PdfViewerPreferencesImp.getViewerPreferences(catalog)
3073: .getPageLayoutAndMode();
3074: }
3075:
3076: /**
3077: * Getter for property appendable.
3078: * @return Value of property appendable.
3079: */
3080: public boolean isAppendable() {
3081: return this .appendable;
3082: }
3083:
3084: /**
3085: * Setter for property appendable.
3086: * @param appendable New value of property appendable.
3087: */
3088: public void setAppendable(boolean appendable) {
3089: this .appendable = appendable;
3090: if (appendable)
3091: getPdfObject(trailer.get(PdfName.ROOT));
3092: }
3093:
3094: /**
3095: * Getter for property newXrefType.
3096: * @return Value of property newXrefType.
3097: */
3098: public boolean isNewXrefType() {
3099: return newXrefType;
3100: }
3101:
3102: /**
3103: * Getter for property fileLength.
3104: * @return Value of property fileLength.
3105: */
3106: public int getFileLength() {
3107: return fileLength;
3108: }
3109:
3110: /**
3111: * Getter for property hybridXref.
3112: * @return Value of property hybridXref.
3113: */
3114: public boolean isHybridXref() {
3115: return hybridXref;
3116: }
3117:
3118: static class PageRefs {
3119: private PdfReader reader;
3120: private IntHashtable refsp;
3121: private ArrayList refsn;
3122: private ArrayList pageInh;
3123: private int lastPageRead = -1;
3124: private int sizep;
3125:
3126: private PageRefs(PdfReader reader) throws IOException {
3127: this .reader = reader;
3128: if (reader.partial) {
3129: refsp = new IntHashtable();
3130: PdfNumber npages = (PdfNumber) PdfReader
3131: .getPdfObjectRelease(reader.rootPages
3132: .get(PdfName.COUNT));
3133: sizep = npages.intValue();
3134: } else {
3135: readPages();
3136: }
3137: }
3138:
3139: PageRefs(PageRefs other, PdfReader reader) {
3140: this .reader = reader;
3141: this .sizep = other.sizep;
3142: if (other.refsn != null) {
3143: refsn = new ArrayList(other.refsn);
3144: for (int k = 0; k < refsn.size(); ++k) {
3145: refsn.set(k, duplicatePdfObject((PdfObject) refsn
3146: .get(k), reader));
3147: }
3148: } else
3149: this .refsp = (IntHashtable) other.refsp.clone();
3150: }
3151:
3152: int size() {
3153: if (refsn != null)
3154: return refsn.size();
3155: else
3156: return sizep;
3157: }
3158:
3159: void readPages() throws IOException {
3160: if (refsn != null)
3161: return;
3162: refsp = null;
3163: refsn = new ArrayList();
3164: pageInh = new ArrayList();
3165: iteratePages((PRIndirectReference) reader.catalog
3166: .get(PdfName.PAGES));
3167: pageInh = null;
3168: reader.rootPages.put(PdfName.COUNT, new PdfNumber(refsn
3169: .size()));
3170: }
3171:
3172: void reReadPages() throws IOException {
3173: refsn = null;
3174: readPages();
3175: }
3176:
3177: /** Gets the dictionary that represents a page.
3178: * @param pageNum the page number. 1 is the first
3179: * @return the page dictionary
3180: */
3181: public PdfDictionary getPageN(int pageNum) {
3182: PRIndirectReference ref = getPageOrigRef(pageNum);
3183: return (PdfDictionary) PdfReader.getPdfObject(ref);
3184: }
3185:
3186: /**
3187: * @param pageNum
3188: * @return a dictionary object
3189: */
3190: public PdfDictionary getPageNRelease(int pageNum) {
3191: PdfDictionary page = getPageN(pageNum);
3192: releasePage(pageNum);
3193: return page;
3194: }
3195:
3196: /**
3197: * @param pageNum
3198: * @return an indirect reference
3199: */
3200: public PRIndirectReference getPageOrigRefRelease(int pageNum) {
3201: PRIndirectReference ref = getPageOrigRef(pageNum);
3202: releasePage(pageNum);
3203: return ref;
3204: }
3205:
3206: /** Gets the page reference to this page.
3207: * @param pageNum the page number. 1 is the first
3208: * @return the page reference
3209: */
3210: public PRIndirectReference getPageOrigRef(int pageNum) {
3211: try {
3212: --pageNum;
3213: if (pageNum < 0 || pageNum >= size())
3214: return null;
3215: if (refsn != null)
3216: return (PRIndirectReference) refsn.get(pageNum);
3217: else {
3218: int n = refsp.get(pageNum);
3219: if (n == 0) {
3220: PRIndirectReference ref = getSinglePage(pageNum);
3221: if (reader.lastXrefPartial == -1)
3222: lastPageRead = -1;
3223: else
3224: lastPageRead = pageNum;
3225: reader.lastXrefPartial = -1;
3226: refsp.put(pageNum, ref.getNumber());
3227: return ref;
3228: } else {
3229: if (lastPageRead != pageNum)
3230: lastPageRead = -1;
3231: return new PRIndirectReference(reader, n);
3232: }
3233: }
3234: } catch (Exception e) {
3235: throw new ExceptionConverter(e);
3236: }
3237: }
3238:
3239: /**
3240: * @param pageNum
3241: */
3242: public void releasePage(int pageNum) {
3243: if (refsp == null)
3244: return;
3245: --pageNum;
3246: if (pageNum < 0 || pageNum >= size())
3247: return;
3248: if (pageNum != lastPageRead)
3249: return;
3250: lastPageRead = -1;
3251: reader.lastXrefPartial = refsp.get(pageNum);
3252: reader.releaseLastXrefPartial();
3253: refsp.remove(pageNum);
3254: }
3255:
3256: /**
3257: *
3258: */
3259: public void resetReleasePage() {
3260: if (refsp == null)
3261: return;
3262: lastPageRead = -1;
3263: }
3264:
3265: void insertPage(int pageNum, PRIndirectReference ref) {
3266: --pageNum;
3267: if (refsn != null) {
3268: if (pageNum >= refsn.size())
3269: refsn.add(ref);
3270: else
3271: refsn.add(pageNum, ref);
3272: } else {
3273: ++sizep;
3274: lastPageRead = -1;
3275: if (pageNum >= size()) {
3276: refsp.put(size(), ref.getNumber());
3277: } else {
3278: IntHashtable refs2 = new IntHashtable(
3279: (refsp.size() + 1) * 2);
3280: for (Iterator it = refsp.getEntryIterator(); it
3281: .hasNext();) {
3282: IntHashtable.Entry entry = (IntHashtable.Entry) it
3283: .next();
3284: int p = entry.getKey();
3285: refs2.put(p >= pageNum ? p + 1 : p, entry
3286: .getValue());
3287: }
3288: refs2.put(pageNum, ref.getNumber());
3289: refsp = refs2;
3290: }
3291: }
3292: }
3293:
3294: private void pushPageAttributes(PdfDictionary nodePages) {
3295: PdfDictionary dic = new PdfDictionary();
3296: if (!pageInh.isEmpty()) {
3297: dic.putAll((PdfDictionary) pageInh
3298: .get(pageInh.size() - 1));
3299: }
3300: for (int k = 0; k < pageInhCandidates.length; ++k) {
3301: PdfObject obj = nodePages.get(pageInhCandidates[k]);
3302: if (obj != null)
3303: dic.put(pageInhCandidates[k], obj);
3304: }
3305: pageInh.add(dic);
3306: }
3307:
3308: private void popPageAttributes() {
3309: pageInh.remove(pageInh.size() - 1);
3310: }
3311:
3312: private void iteratePages(PRIndirectReference rpage)
3313: throws IOException {
3314: PdfDictionary page = (PdfDictionary) getPdfObject(rpage);
3315: PdfArray kidsPR = (PdfArray) getPdfObject(page
3316: .get(PdfName.KIDS));
3317: if (kidsPR == null) {
3318: page.put(PdfName.TYPE, PdfName.PAGE);
3319: PdfDictionary dic = (PdfDictionary) pageInh.get(pageInh
3320: .size() - 1);
3321: PdfName key;
3322: for (Iterator i = dic.getKeys().iterator(); i.hasNext();) {
3323: key = (PdfName) i.next();
3324: if (page.get(key) == null)
3325: page.put(key, dic.get(key));
3326: }
3327: if (page.get(PdfName.MEDIABOX) == null) {
3328: PdfArray arr = new PdfArray(new float[] { 0, 0,
3329: PageSize.LETTER.getRight(),
3330: PageSize.LETTER.getTop() });
3331: page.put(PdfName.MEDIABOX, arr);
3332: }
3333: refsn.add(rpage);
3334: } else {
3335: page.put(PdfName.TYPE, PdfName.PAGES);
3336: pushPageAttributes(page);
3337: ArrayList kids = kidsPR.getArrayList();
3338: for (int k = 0; k < kids.size(); ++k) {
3339: PdfObject obj = (PdfObject) kids.get(k);
3340: if (!obj.isIndirect()) {
3341: while (k < kids.size())
3342: kids.remove(k);
3343: break;
3344: }
3345: iteratePages((PRIndirectReference) obj);
3346: }
3347: popPageAttributes();
3348: }
3349: }
3350:
3351: protected PRIndirectReference getSinglePage(int n) {
3352: PdfDictionary acc = new PdfDictionary();
3353: PdfDictionary top = reader.rootPages;
3354: int base = 0;
3355: while (true) {
3356: for (int k = 0; k < pageInhCandidates.length; ++k) {
3357: PdfObject obj = top.get(pageInhCandidates[k]);
3358: if (obj != null)
3359: acc.put(pageInhCandidates[k], obj);
3360: }
3361: PdfArray kids = (PdfArray) PdfReader
3362: .getPdfObjectRelease(top.get(PdfName.KIDS));
3363: for (Iterator it = kids.listIterator(); it.hasNext();) {
3364: PRIndirectReference ref = (PRIndirectReference) it
3365: .next();
3366: PdfDictionary dic = (PdfDictionary) getPdfObject(ref);
3367: int last = reader.lastXrefPartial;
3368: PdfObject count = getPdfObjectRelease(dic
3369: .get(PdfName.COUNT));
3370: reader.lastXrefPartial = last;
3371: int acn = 1;
3372: if (count != null
3373: && count.type() == PdfObject.NUMBER)
3374: acn = ((PdfNumber) count).intValue();
3375: if (n < base + acn) {
3376: if (count == null) {
3377: dic.mergeDifferent(acc);
3378: return ref;
3379: }
3380: reader.releaseLastXrefPartial();
3381: top = dic;
3382: break;
3383: }
3384: reader.releaseLastXrefPartial();
3385: base += acn;
3386: }
3387: }
3388: }
3389:
3390: private void selectPages(List pagesToKeep) {
3391: IntHashtable pg = new IntHashtable();
3392: ArrayList finalPages = new ArrayList();
3393: int psize = size();
3394: for (Iterator it = pagesToKeep.iterator(); it.hasNext();) {
3395: Integer pi = (Integer) it.next();
3396: int p = pi.intValue();
3397: if (p >= 1 && p <= psize && pg.put(p, 1) == 0)
3398: finalPages.add(pi);
3399: }
3400: if (reader.partial) {
3401: for (int k = 1; k <= psize; ++k) {
3402: getPageOrigRef(k);
3403: resetReleasePage();
3404: }
3405: }
3406: PRIndirectReference parent = (PRIndirectReference) reader.catalog
3407: .get(PdfName.PAGES);
3408: PdfDictionary topPages = (PdfDictionary) PdfReader
3409: .getPdfObject(parent);
3410: ArrayList newPageRefs = new ArrayList(finalPages.size());
3411: PdfArray kids = new PdfArray();
3412: for (int k = 0; k < finalPages.size(); ++k) {
3413: int p = ((Integer) finalPages.get(k)).intValue();
3414: PRIndirectReference pref = getPageOrigRef(p);
3415: resetReleasePage();
3416: kids.add(pref);
3417: newPageRefs.add(pref);
3418: getPageN(p).put(PdfName.PARENT, parent);
3419: }
3420: AcroFields af = reader.getAcroFields();
3421: boolean removeFields = (af.getFields().size() > 0);
3422: for (int k = 1; k <= psize; ++k) {
3423: if (!pg.containsKey(k)) {
3424: if (removeFields)
3425: af.removeFieldsFromPage(k);
3426: PRIndirectReference pref = getPageOrigRef(k);
3427: int nref = pref.getNumber();
3428: reader.xrefObj.set(nref, null);
3429: if (reader.partial) {
3430: reader.xref[nref * 2] = -1;
3431: reader.xref[nref * 2 + 1] = 0;
3432: }
3433: }
3434: }
3435: topPages.put(PdfName.COUNT,
3436: new PdfNumber(finalPages.size()));
3437: topPages.put(PdfName.KIDS, kids);
3438: refsp = null;
3439: refsn = newPageRefs;
3440: }
3441: }
3442:
3443: PdfIndirectReference getCryptoRef() {
3444: if (cryptoRef == null)
3445: return null;
3446: return new PdfIndirectReference(0, cryptoRef.getNumber(),
3447: cryptoRef.getGeneration());
3448: }
3449:
3450: /**
3451: * Removes any usage rights that this PDF may have. Only Adobe can grant usage rights
3452: * and any PDF modification with iText will invalidate them. Invalidated usage rights may
3453: * confuse Acrobat and it's advisabe to remove them altogether.
3454: */
3455: public void removeUsageRights() {
3456: PdfDictionary perms = (PdfDictionary) getPdfObject(catalog
3457: .get(PdfName.PERMS));
3458: if (perms == null)
3459: return;
3460: perms.remove(PdfName.UR);
3461: perms.remove(PdfName.UR3);
3462: if (perms.size() == 0)
3463: catalog.remove(PdfName.PERMS);
3464: }
3465:
3466: /**
3467: * Gets the certification level for this document. The return values can be <code>PdfSignatureAppearance.NOT_CERTIFIED</code>,
3468: * <code>PdfSignatureAppearance.CERTIFIED_NO_CHANGES_ALLOWED</code>,
3469: * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING</code> and
3470: * <code>PdfSignatureAppearance.CERTIFIED_FORM_FILLING_AND_ANNOTATIONS</code>.
3471: * <p>
3472: * No signature validation is made, use the methods availabe for that in <CODE>AcroFields</CODE>.
3473: * </p>
3474: * @return gets the certification level for this document
3475: */
3476: public int getCertificationLevel() {
3477: PdfDictionary dic = (PdfDictionary) getPdfObject(catalog
3478: .get(PdfName.PERMS));
3479: if (dic == null)
3480: return PdfSignatureAppearance.NOT_CERTIFIED;
3481: dic = (PdfDictionary) getPdfObject(dic.get(PdfName.DOCMDP));
3482: if (dic == null)
3483: return PdfSignatureAppearance.NOT_CERTIFIED;
3484: PdfArray arr = (PdfArray) getPdfObject(dic
3485: .get(PdfName.REFERENCE));
3486: if (arr == null || arr.size() == 0)
3487: return PdfSignatureAppearance.NOT_CERTIFIED;
3488: dic = (PdfDictionary) getPdfObject((PdfObject) (arr
3489: .getArrayList().get(0)));
3490: if (dic == null)
3491: return PdfSignatureAppearance.NOT_CERTIFIED;
3492: dic = (PdfDictionary) getPdfObject(dic
3493: .get(PdfName.TRANSFORMPARAMS));
3494: if (dic == null)
3495: return PdfSignatureAppearance.NOT_CERTIFIED;
3496: PdfNumber p = (PdfNumber) getPdfObject(dic.get(PdfName.P));
3497: if (p == null)
3498: return PdfSignatureAppearance.NOT_CERTIFIED;
3499: return p.intValue();
3500: }
3501:
3502: /**
3503: * Checks if the document was opened with the owner password so that the end application
3504: * can decide what level of access restrictions to apply. If the document is not encrypted
3505: * it will return <CODE>true</CODE>.
3506: * @return <CODE>true</CODE> if the document was opened with the owner password or if it's not encrypted,
3507: * <CODE>false</CODE> if the document was opened with the user password
3508: */
3509: public final boolean isOpenedWithFullPermissions() {
3510: return !encrypted || ownerPasswordUsed;
3511: }
3512:
3513: public int getCryptoMode() {
3514: if (decrypt == null)
3515: return -1;
3516: else
3517: return decrypt.getCryptoMode();
3518: }
3519:
3520: public boolean isMetadataEncrypted() {
3521: if (decrypt == null)
3522: return false;
3523: else
3524: return decrypt.isMetadataEncrypted();
3525: }
3526:
3527: public byte[] computeUserPassword() {
3528: if (!encrypted || !ownerPasswordUsed)
3529: return null;
3530: return decrypt.computeUserPassword(password);
3531: }
3532: }
|