aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/lowagie/text/pdf/PdfReader.java
diff options
context:
space:
mode:
authortknall <tknall@7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c>2006-12-01 12:20:24 +0000
committertknall <tknall@7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c>2006-12-01 12:20:24 +0000
commit6025b6016517c6d898d8957d1d7e03ba71431912 (patch)
treeb15bd6fa5ffe9588a9bca3f2b8a7e358f83b6eba /src/main/java/com/lowagie/text/pdf/PdfReader.java
parentd2c77e820ab4aba8235d71275755021347b3ad10 (diff)
downloadpdf-as-3-6025b6016517c6d898d8957d1d7e03ba71431912.tar.gz
pdf-as-3-6025b6016517c6d898d8957d1d7e03ba71431912.tar.bz2
pdf-as-3-6025b6016517c6d898d8957d1d7e03ba71431912.zip
Initial import of release 2.2.REL-2.2@923
git-svn-id: https://joinup.ec.europa.eu/svn/pdf-as/trunk@4 7b5415b0-85f9-ee4d-85bd-d5d0c3b42d1c
Diffstat (limited to 'src/main/java/com/lowagie/text/pdf/PdfReader.java')
-rw-r--r--src/main/java/com/lowagie/text/pdf/PdfReader.java3172
1 files changed, 3172 insertions, 0 deletions
diff --git a/src/main/java/com/lowagie/text/pdf/PdfReader.java b/src/main/java/com/lowagie/text/pdf/PdfReader.java
new file mode 100644
index 0000000..da46174
--- /dev/null
+++ b/src/main/java/com/lowagie/text/pdf/PdfReader.java
@@ -0,0 +1,3172 @@
+/*
+ * $Id: PdfReader.java,v 1.76 2006/05/31 16:23:26 psoares33 Exp $
+ * $Name: $
+ *
+ * Copyright 2001, 2002 Paulo Soares
+ *
+ * The contents of this file are subject to the Mozilla Public License Version 1.1
+ * (the "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the License.
+ *
+ * The Original Code is 'iText, a free JAVA-PDF library'.
+ *
+ * The Initial Developer of the Original Code is Bruno Lowagie. Portions created by
+ * the Initial Developer are Copyright (C) 1999, 2000, 2001, 2002 by Bruno Lowagie.
+ * All Rights Reserved.
+ * Co-Developer of the code is Paulo Soares. Portions created by the Co-Developer
+ * are Copyright (C) 2000, 2001, 2002 by Paulo Soares. All Rights Reserved.
+ *
+ * Contributor(s): all the names of the contributors are added in the source code
+ * where applicable.
+ *
+ * Alternatively, the contents of this file may be used under the terms of the
+ * LGPL license (the "GNU LIBRARY GENERAL PUBLIC LICENSE"), in which case the
+ * provisions of LGPL are applicable instead of those above. If you wish to
+ * allow use of your version of this file only under the terms of the LGPL
+ * License and not to allow others to use your version of this file under
+ * the MPL, indicate your decision by deleting the provisions above and
+ * replace them with the notice and other provisions required by the LGPL.
+ * If you do not delete the provisions above, a recipient may use your version
+ * of this file under either the MPL or the GNU LIBRARY GENERAL PUBLIC LICENSE.
+ *
+ * This library is free software; you can redistribute it and/or modify it
+ * under the terms of the MPL as stated above or under the terms of the GNU
+ * Library General Public License as published by the Free Software Foundation;
+ * either version 2 of the License, or any later version.
+ *
+ * This library is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU Library general Public License for more
+ * details.
+ *
+ * If you didn't download this code from the following link, you should check if
+ * you aren't using an obsolete version:
+ * http://www.lowagie.com/iText/
+ */
+
+package com.lowagie.text.pdf;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.Iterator;
+import java.util.zip.InflaterInputStream;
+import java.util.Arrays;
+import java.util.Collections;
+
+import com.lowagie.text.Rectangle;
+import com.lowagie.text.PageSize;
+import com.lowagie.text.StringCompare;
+import com.lowagie.text.ExceptionConverter;
+
+/** Reads a PDF document.
+ * @author Paulo Soares (psoares@consiste.pt)
+ * @author Kazuya Ujihara
+ */
+public class PdfReader {
+
+ static final PdfName pageInhCandidates[] = {
+ PdfName.MEDIABOX, PdfName.ROTATE, PdfName.RESOURCES, PdfName.CROPBOX
+ };
+
+ static final PdfName vpnames[] = {PdfName.HIDETOOLBAR, PdfName.HIDEMENUBAR,
+ PdfName.HIDEWINDOWUI, PdfName.FITWINDOW, PdfName.CENTERWINDOW, PdfName.DISPLAYDOCTITLE};
+ static final int vpints[] = {PdfWriter.HideToolbar, PdfWriter.HideMenubar,
+ PdfWriter.HideWindowUI, PdfWriter.FitWindow, PdfWriter.CenterWindow, PdfWriter.DisplayDocTitle};
+
+ static final byte endstream[] = PdfEncodings.convertToBytes("endstream", null);
+ static final byte endobj[] = PdfEncodings.convertToBytes("endobj", null);
+ protected PRTokeniser tokens;
+ // Each xref pair is a position
+ // type 0 -> -1, 0
+ // type 1 -> offset, 0
+ // type 2 -> index, obj num
+ protected int xref[];
+ protected HashMap objStmMark;
+ protected IntHashtable objStmToOffset;
+ protected boolean newXrefType;
+ private ArrayList xrefObj;
+ PdfDictionary rootPages;
+ protected PdfDictionary trailer;
+ //protected ArrayList pages;
+ protected PdfDictionary catalog;
+ protected PageRefs pageRefs;
+ protected PRAcroForm acroForm = null;
+ protected boolean acroFormParsed = false;
+ protected ArrayList pageInh;
+ protected boolean encrypted = false;
+ protected boolean rebuilt = false;
+ protected int freeXref;
+ protected boolean tampered = false;
+ protected int lastXref;
+ protected int eofPos;
+ protected char pdfVersion;
+ protected PdfEncryption decrypt;
+ protected byte password[] = null; //added by ujihara for decryption
+ protected ArrayList strings = new ArrayList();
+ protected boolean sharedStreams = true;
+ protected boolean consolidateNamedDestinations = false;
+ protected int rValue;
+ protected int pValue;
+ private int objNum;
+ private int objGen;
+ private boolean visited[];
+ private IntHashtable newHits;
+ private int fileLength;
+ private boolean hybridXref;
+ private int lastXrefPartial = -1;
+ private boolean partial;
+ private PRIndirectReference cryptoRef;
+
+ /**
+ * Holds value of property appendable.
+ */
+ private boolean appendable;
+
+ protected PdfReader() {
+ }
+
+ /** Reads and parses a PDF document.
+ * @param filename the file name of the document
+ * @throws IOException on error
+ */
+ public PdfReader(String filename) throws IOException {
+ this(filename, null);
+ }
+
+ /** Reads and parses a PDF document.
+ * @param filename the file name of the document
+ * @param ownerPassword the password to read the document
+ * @throws IOException on error
+ */
+ public PdfReader(String filename, byte ownerPassword[]) throws IOException {
+ password = ownerPassword;
+ tokens = new PRTokeniser(filename);
+ readPdf();
+ }
+
+ /** Reads and parses a PDF document.
+ * @param pdfIn the byte array with the document
+ * @throws IOException on error
+ */
+ public PdfReader(byte pdfIn[]) throws IOException {
+ this(pdfIn, null);
+ }
+
+ /** Reads and parses a PDF document.
+ * @param pdfIn the byte array with the document
+ * @param ownerPassword the password to read the document
+ * @throws IOException on error
+ */
+ public PdfReader(byte pdfIn[], byte ownerPassword[]) throws IOException {
+ password = ownerPassword;
+ tokens = new PRTokeniser(pdfIn);
+ readPdf();
+ }
+
+ /** Reads and parses a PDF document.
+ * @param url the URL of the document
+ * @throws IOException on error
+ */
+ public PdfReader(URL url) throws IOException {
+ this(url, null);
+ }
+
+ /** Reads and parses a PDF document.
+ * @param url the URL of the document
+ * @param ownerPassword the password to read the document
+ * @throws IOException on error
+ */
+ public PdfReader(URL url, byte ownerPassword[]) throws IOException {
+ password = ownerPassword;
+ tokens = new PRTokeniser(new RandomAccessFileOrArray(url));
+ readPdf();
+ }
+
+ /**
+ * Reads and parses a PDF document.
+ * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
+ * end but is not closed
+ * @param ownerPassword the password to read the document
+ * @throws IOException on error
+ */
+ public PdfReader(InputStream is, byte ownerPassword[]) throws IOException {
+ password = ownerPassword;
+ tokens = new PRTokeniser(new RandomAccessFileOrArray(is));
+ readPdf();
+ }
+
+ /**
+ * Reads and parses a PDF document.
+ * @param is the <CODE>InputStream</CODE> containing the document. The stream is read to the
+ * end but is not closed
+ * @throws IOException on error
+ */
+ public PdfReader(InputStream is) throws IOException {
+ this(is, null);
+ }
+
+ /**
+ * Reads and parses a pdf document. Contrary to the other constructors only the xref is read
+ * into memory. The reader is said to be working in "partial" mode as only parts of the pdf
+ * are read as needed. The pdf is left open but may be closed at any time with
+ * <CODE>PdfReader.close()</CODE>, reopen is automatic.
+ * @param raf the document location
+ * @param ownerPassword the password or <CODE>null</CODE> for no password
+ * @throws IOException on error
+ */
+ public PdfReader(RandomAccessFileOrArray raf, byte ownerPassword[]) throws IOException {
+ password = ownerPassword;
+ partial = true;
+ tokens = new PRTokeniser(raf);
+ readPdfPartial();
+ }
+
+ /** Creates an independent duplicate.
+ * @param reader the <CODE>PdfReader</CODE> to duplicate
+ */
+ public PdfReader(PdfReader reader) {
+ this.appendable = reader.appendable;
+ this.consolidateNamedDestinations = reader.consolidateNamedDestinations;
+ this.encrypted = reader.encrypted;
+ this.rebuilt = reader.rebuilt;
+ this.sharedStreams = reader.sharedStreams;
+ this.tampered = reader.tampered;
+ this.password = reader.password;
+ this.pdfVersion = reader.pdfVersion;
+ this.eofPos = reader.eofPos;
+ this.freeXref = reader.freeXref;
+ this.lastXref = reader.lastXref;
+ this.tokens = new PRTokeniser(reader.tokens.getSafeFile());
+ if (reader.decrypt != null)
+ this.decrypt = new PdfEncryption(reader.decrypt);
+ this.pValue = reader.pValue;
+ this.rValue = reader.rValue;
+ this.xrefObj = new ArrayList(reader.xrefObj);
+ for (int k = 0; k < reader.xrefObj.size(); ++k) {
+ this.xrefObj.set(k, duplicatePdfObject((PdfObject)reader.xrefObj.get(k), this));
+ }
+ this.pageRefs = new PageRefs(reader.pageRefs, this);
+ this.trailer = (PdfDictionary)duplicatePdfObject(reader.trailer, this);
+ this.catalog = (PdfDictionary)getPdfObject(trailer.get(PdfName.ROOT));
+ this.rootPages = (PdfDictionary)getPdfObject(catalog.get(PdfName.PAGES));
+ this.fileLength = reader.fileLength;
+ this.partial = reader.partial;
+ this.hybridXref = reader.hybridXref;
+ this.objStmToOffset = reader.objStmToOffset;
+ this.xref = reader.xref;
+ this.cryptoRef = (PRIndirectReference)duplicatePdfObject(reader.cryptoRef, this);
+ }
+
+ /** Gets a new file instance of the original PDF
+ * document.
+ * @return a new file instance of the original PDF document
+ */
+ public RandomAccessFileOrArray getSafeFile() {
+ return tokens.getSafeFile();
+ }
+
+ protected PdfReaderInstance getPdfReaderInstance(PdfWriter writer) {
+ return new PdfReaderInstance(this, writer);
+ }
+
+ /** Gets the number of pages in the document.
+ * @return the number of pages in the document
+ */
+ public int getNumberOfPages() {
+ return pageRefs.size();
+ }
+
+ /** Returns the document's catalog. This dictionary is not a copy,
+ * any changes will be reflected in the catalog.
+ * @return the document's catalog
+ */
+ public PdfDictionary getCatalog() {
+ return catalog;
+ }
+
+ /** Returns the document's acroform, if it has one.
+ * @return the document's acroform
+ */
+ public PRAcroForm getAcroForm() {
+ if (!acroFormParsed) {
+ acroFormParsed = true;
+ PdfObject form = catalog.get(PdfName.ACROFORM);
+ if (form != null) {
+ try {
+ acroForm = new PRAcroForm(this);
+ acroForm.readAcroForm((PdfDictionary)getPdfObject(form));
+ }
+ catch (Exception e) {
+ acroForm = null;
+ }
+ }
+ }
+ return acroForm;
+ }
+ /**
+ * Gets the page rotation. This value can be 0, 90, 180 or 270.
+ * @param index the page number. The first page is 1
+ * @return the page rotation
+ */
+ public int getPageRotation(int index) {
+ return getPageRotation(pageRefs.getPageNRelease(index));
+ }
+
+ int getPageRotation(PdfDictionary page) {
+ PdfNumber rotate = (PdfNumber)getPdfObject(page.get(PdfName.ROTATE));
+ if (rotate == null)
+ return 0;
+ else {
+ int n = rotate.intValue();
+ n %= 360;
+ return n < 0 ? n + 360 : n;
+ }
+ }
+ /** Gets the page size, taking rotation into account. This
+ * is a <CODE>Rectangle</CODE> with the value of the /MediaBox and the /Rotate key.
+ * @param index the page number. The first page is 1
+ * @return a <CODE>Rectangle</CODE>
+ */
+ public Rectangle getPageSizeWithRotation(int index) {
+ return getPageSizeWithRotation(pageRefs.getPageNRelease(index));
+ }
+
+ /**
+ * Gets the rotated page from a page dictionary.
+ * @param page the page dictionary
+ * @return the rotated page
+ */
+ public Rectangle getPageSizeWithRotation(PdfDictionary page) {
+ Rectangle rect = getPageSize(page);
+ int rotation = getPageRotation(page);
+ while (rotation > 0) {
+ rect = rect.rotate();
+ rotation -= 90;
+ }
+ return rect;
+ }
+
+ /** Gets the page size without taking rotation into account. This
+ * is the value of the /MediaBox key.
+ * @param index the page number. The first page is 1
+ * @return the page size
+ */
+ public Rectangle getPageSize(int index) {
+ return getPageSize(pageRefs.getPageNRelease(index));
+ }
+
+ /**
+ * Gets the page from a page dictionary
+ * @param page the page dictionary
+ * @return the page
+ */
+ public Rectangle getPageSize(PdfDictionary page) {
+ PdfArray mediaBox = (PdfArray)getPdfObject(page.get(PdfName.MEDIABOX));
+ return getNormalizedRectangle(mediaBox);
+ }
+
+ /** Gets the crop box without taking rotation into account. This
+ * is the value of the /CropBox key. The crop box is the part
+ * of the document to be displayed or printed. It usually is the same
+ * as the media box but may be smaller. If the page doesn't have a crop
+ * box the page size will be returned.
+ * @param index the page number. The first page is 1
+ * @return the crop box
+ */
+ public Rectangle getCropBox(int index) {
+ PdfDictionary page = pageRefs.getPageNRelease(index);
+ PdfArray cropBox = (PdfArray)getPdfObjectRelease(page.get(PdfName.CROPBOX));
+ if (cropBox == null)
+ return getPageSize(page);
+ return getNormalizedRectangle(cropBox);
+ }
+
+ /** Gets the box size. Allowed names are: "crop", "trim", "art", "bleed" and "media".
+ * @param index the page number. The first page is 1
+ * @param boxName the box name
+ * @return the box rectangle or null
+ */
+ public Rectangle getBoxSize(int index, String boxName) {
+ PdfDictionary page = pageRefs.getPageNRelease(index);
+ PdfArray box = null;
+ if (boxName.equals("trim"))
+ box = (PdfArray)getPdfObjectRelease(page.get(PdfName.TRIMBOX));
+ else if (boxName.equals("art"))
+ box = (PdfArray)getPdfObjectRelease(page.get(PdfName.ARTBOX));
+ else if (boxName.equals("bleed"))
+ box = (PdfArray)getPdfObjectRelease(page.get(PdfName.BLEEDBOX));
+ else if (boxName.equals("crop"))
+ box = (PdfArray)getPdfObjectRelease(page.get(PdfName.CROPBOX));
+ else if (boxName.equals("media"))
+ box = (PdfArray)getPdfObjectRelease(page.get(PdfName.MEDIABOX));
+ if (box == null)
+ return null;
+ return getNormalizedRectangle(box);
+ }
+
+ /** Returns the content of the document information dictionary as a <CODE>HashMap</CODE>
+ * of <CODE>String</CODE>.
+ * @return content of the document information dictionary
+ */
+ public HashMap getInfo() {
+ HashMap map = new HashMap();
+ PdfDictionary info = (PdfDictionary)getPdfObject(trailer.get(PdfName.INFO));
+ if (info == null)
+ return map;
+ for (Iterator it = info.getKeys().iterator(); it.hasNext();) {
+ PdfName key = (PdfName)it.next();
+ PdfObject obj = getPdfObject(info.get(key));
+ if (obj == null)
+ continue;
+ String value = obj.toString();
+ switch (obj.type()) {
+ case PdfObject.STRING: {
+ value = ((PdfString)obj).toUnicodeString();
+ break;
+ }
+ case PdfObject.NAME: {
+ value = PdfName.decodeName(value);
+ break;
+ }
+ }
+ map.put(PdfName.decodeName(key.toString()), value);
+ }
+ return map;
+ }
+
+ /** Normalizes a <CODE>Rectangle</CODE> so that llx and lly are smaller than urx and ury.
+ * @param box the original rectangle
+ * @return a normalized <CODE>Rectangle</CODE>
+ */
+ public static Rectangle getNormalizedRectangle(PdfArray box) {
+ ArrayList rect = box.getArrayList();
+ float llx = ((PdfNumber)rect.get(0)).floatValue();
+ float lly = ((PdfNumber)rect.get(1)).floatValue();
+ float urx = ((PdfNumber)rect.get(2)).floatValue();
+ float ury = ((PdfNumber)rect.get(3)).floatValue();
+ return new Rectangle(Math.min(llx, urx), Math.min(lly, ury),
+ Math.max(llx, urx), Math.max(lly, ury));
+ }
+
+ protected void readPdf() throws IOException {
+ try {
+ fileLength = tokens.getFile().length();
+ pdfVersion = tokens.checkPdfHeader();
+ try {
+ readXref();
+ }
+ catch (Exception e) {
+ try {
+ rebuilt = true;
+ rebuildXref();
+ lastXref = -1;
+ }
+ catch (Exception ne) {
+ throw new IOException("Rebuild failed: " + ne.getMessage() + "; Original message: " + e.getMessage());
+ }
+ }
+ try {
+ readDocObj();
+ }
+ catch (IOException ne) {
+ if (rebuilt)
+ throw ne;
+ rebuilt = true;
+ encrypted = false;
+ rebuildXref();
+ lastXref = -1;
+ readDocObj();
+ }
+
+ strings.clear();
+ readPages();
+ eliminateSharedStreams();
+ removeUnusedObjects();
+ }
+ finally {
+ try {
+ tokens.close();
+ }
+ catch (Exception e) {
+ // empty on purpose
+ }
+ }
+ }
+
+ protected void readPdfPartial() throws IOException {
+ try {
+ fileLength = tokens.getFile().length();
+ pdfVersion = tokens.checkPdfHeader();
+ try {
+ readXref();
+ }
+ catch (Exception e) {
+ try {
+ rebuilt = true;
+ rebuildXref();
+ lastXref = -1;
+ }
+ catch (Exception ne) {
+ throw new IOException("Rebuild failed: " + ne.getMessage() + "; Original message: " + e.getMessage());
+ }
+ }
+ readDocObjPartial();
+ readPages();
+ }
+ catch (IOException e) {
+ try{tokens.close();}catch(Exception ee){}
+ throw e;
+ }
+ }
+
+ private boolean equalsArray(byte ar1[], byte ar2[], int size) {
+ for (int k = 0; k < size; ++k) {
+ if (ar1[k] != ar2[k])
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * @throws IOException
+ */
+ private void readDecryptedDocObj() throws IOException {
+ if (encrypted)
+ return;
+ PdfObject encDic = trailer.get(PdfName.ENCRYPT);
+ if (encDic == null || encDic.toString().equals("null"))
+ return;
+ encrypted = true;
+ PdfDictionary enc = (PdfDictionary)getPdfObject(encDic);
+
+ String s;
+ PdfObject o;
+
+ PdfArray documentIDs = (PdfArray)getPdfObject(trailer.get(PdfName.ID));
+ byte documentID[] = null;
+ if (documentIDs != null) {
+ o = (PdfObject)documentIDs.getArrayList().get(0);
+ s = o.toString();
+ documentID = com.lowagie.text.DocWriter.getISOBytes(s);
+ }
+
+ s = enc.get(PdfName.U).toString();
+ byte uValue[] = com.lowagie.text.DocWriter.getISOBytes(s);
+ s = enc.get(PdfName.O).toString();
+ byte oValue[] = com.lowagie.text.DocWriter.getISOBytes(s);
+
+ o = enc.get(PdfName.R);
+ if (!o.isNumber()) throw new IOException("Illegal R value.");
+ rValue = ((PdfNumber)o).intValue();
+ if (rValue != 2 && rValue != 3) throw new IOException("Unknown encryption type (" + rValue + ")");
+
+ o = enc.get(PdfName.P);
+ if (!o.isNumber()) throw new IOException("Illegal P value.");
+ pValue = ((PdfNumber)o).intValue();
+
+ // get the Keylength if Revision is 3
+ int lengthValue;
+ if ( rValue == 3 ){
+ o = enc.get(PdfName.LENGTH);
+ if (!o.isNumber())
+ throw new IOException("Illegal Length value.");
+ lengthValue = ( (PdfNumber) o).intValue();
+ if (lengthValue > 128 || lengthValue < 40 || lengthValue % 8 != 0)
+ throw new IOException("Illegal Length value.");
+ } else {
+ // Keylength is 40 bit in revision 2
+ lengthValue=40;
+ }
+
+
+
+ decrypt = new PdfEncryption();
+
+ //check by user password
+ decrypt.setupByUserPassword(documentID, password, oValue, pValue, lengthValue, rValue);
+ if (!equalsArray(uValue, decrypt.userKey, rValue == 3 ? 16 : 32)) {
+ //check by owner password
+ decrypt.setupByOwnerPassword(documentID, password, uValue, oValue, pValue, lengthValue, rValue);
+ if (!equalsArray(uValue, decrypt.userKey, rValue == 3 ? 16 : 32)) {
+ throw new IOException("Bad user password");
+ }
+ }
+ for (int k = 0; k < strings.size(); ++k) {
+ PdfString str = (PdfString)strings.get(k);
+ str.decrypt(this);
+ }
+ if (encDic.isIndirect()) {
+ cryptoRef = (PRIndirectReference)encDic;
+ xrefObj.set(cryptoRef.getNumber(), null);
+ }
+ }
+
+ /**
+ * @param obj
+ * @return a PdfObject
+ */
+ public static PdfObject getPdfObjectRelease(PdfObject obj) {
+ PdfObject obj2 = getPdfObject(obj);
+ releaseLastXrefPartial(obj);
+ return obj2;
+ }
+
+
+ /**
+ * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
+ * if needed.
+ * @param obj the <CODE>PdfObject</CODE> to read
+ * @return the resolved <CODE>PdfObject</CODE>
+ */
+ public static PdfObject getPdfObject(PdfObject obj) {
+ if (obj == null)
+ return null;
+ if (!obj.isIndirect())
+ return obj;
+ try {
+ PRIndirectReference ref = (PRIndirectReference)obj;
+ int idx = ref.getNumber();
+ boolean appendable = ref.getReader().appendable;
+ obj = ref.getReader().getPdfObject(idx);
+ if (obj == null) {
+ if (appendable) {
+ obj = new PdfNull();
+ obj.setIndRef(ref);
+ return obj;
+ }
+ else
+ return PdfNull.PDFNULL;
+ }
+ else {
+ if (appendable) {
+ switch (obj.type()) {
+ case PdfObject.NULL:
+ obj = new PdfNull();
+ break;
+ case PdfObject.BOOLEAN:
+ obj = new PdfBoolean(((PdfBoolean)obj).booleanValue());
+ break;
+ case PdfObject.NAME:
+ obj = new PdfName(obj.getBytes());
+ break;
+ }
+ obj.setIndRef(ref);
+ }
+ return obj;
+ }
+ }
+ catch (Exception e) {
+ throw new ExceptionConverter(e);
+ }
+ }
+
+ /**
+ * Reads a <CODE>PdfObject</CODE> resolving an indirect reference
+ * if needed. If the reader was opened in partial mode the object will be released
+ * to save memory.
+ * @param obj the <CODE>PdfObject</CODE> to read
+ * @param parent
+ * @return a PdfObject
+ */
+ public static PdfObject getPdfObjectRelease(PdfObject obj, PdfObject parent) {
+ PdfObject obj2 = getPdfObject(obj, parent);
+ releaseLastXrefPartial(obj);
+ return obj2;
+ }
+
+ /**
+ * @param obj
+ * @param parent
+ * @return a PdfObject
+ */
+ public static PdfObject getPdfObject(PdfObject obj, PdfObject parent) {
+ if (obj == null)
+ return null;
+ if (!obj.isIndirect()) {
+ PRIndirectReference ref = null;
+ if (parent != null && (ref = parent.getIndRef()) != null && ref.getReader().isAppendable()) {
+ switch (obj.type()) {
+ case PdfObject.NULL:
+ obj = new PdfNull();
+ break;
+ case PdfObject.BOOLEAN:
+ obj = new PdfBoolean(((PdfBoolean)obj).booleanValue());
+ break;
+ case PdfObject.NAME:
+ obj = new PdfName(obj.getBytes());
+ break;
+ }
+ obj.setIndRef(ref);
+ }
+ return obj;
+ }
+ return getPdfObject(obj);
+ }
+
+ /**
+ * @param idx
+ * @return a PdfObject
+ */
+ public PdfObject getPdfObjectRelease(int idx) {
+ PdfObject obj = getPdfObject(idx);
+ releaseLastXrefPartial();
+ return obj;
+ }
+
+ /**
+ * @param idx
+ * @return aPdfObject
+ */
+ public PdfObject getPdfObject(int idx) {
+ try {
+ lastXrefPartial = -1;
+ if (idx < 0 || idx >= xrefObj.size())
+ return null;
+ PdfObject obj = (PdfObject)xrefObj.get(idx);
+ if (!partial || obj != null)
+ return obj;
+ if (idx * 2 >= xref.length)
+ return null;
+ obj = readSingleObject(idx);
+ lastXrefPartial = -1;
+ if (obj != null)
+ lastXrefPartial = idx;
+ return obj;
+ }
+ catch (Exception e) {
+ throw new ExceptionConverter(e);
+ }
+ }
+
+ /**
+ *
+ */
+ public void resetLastXrefPartial() {
+ lastXrefPartial = -1;
+ }
+
+ /**
+ *
+ */
+ public void releaseLastXrefPartial() {
+ if (partial && lastXrefPartial != -1) {
+ xrefObj.set(lastXrefPartial, null);
+ lastXrefPartial = -1;
+ }
+ }
+
+ /**
+ * @param obj
+ */
+ public static void releaseLastXrefPartial(PdfObject obj) {
+ if (obj == null)
+ return;
+ if (!obj.isIndirect())
+ return;
+ PRIndirectReference ref = (PRIndirectReference)obj;
+ PdfReader reader = ref.getReader();
+ if (reader.partial && reader.lastXrefPartial != -1 && reader.lastXrefPartial == ref.getNumber()) {
+ reader.xrefObj.set(reader.lastXrefPartial, null);
+ }
+ reader.lastXrefPartial = -1;
+ }
+
+ private void setXrefPartialObject(int idx, PdfObject obj) {
+ if (!partial || idx < 0)
+ return;
+ xrefObj.set(idx, obj);
+ }
+
+ /**
+ * @param obj
+ * @return an indirect reference
+ */
+ public PRIndirectReference addPdfObject(PdfObject obj) {
+ xrefObj.add(obj);
+ return new PRIndirectReference(this, xrefObj.size() - 1);
+ }
+
+ protected void readPages() throws IOException {
+ pageInh = new ArrayList();
+ catalog = (PdfDictionary)getPdfObject(trailer.get(PdfName.ROOT));
+ rootPages = (PdfDictionary)getPdfObject(catalog.get(PdfName.PAGES));
+ pageRefs = new PageRefs(this);
+ }
+
+ protected void readDocObjPartial() throws IOException {
+ xrefObj = new ArrayList(xref.length / 2);
+ xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
+ readDecryptedDocObj();
+ if (objStmToOffset != null) {
+ int keys[] = objStmToOffset.getKeys();
+ for (int k = 0; k < keys.length; ++k) {
+ int n = keys[k];
+ objStmToOffset.put(n, xref[n * 2]);
+ xref[n * 2] = -1;
+ }
+ }
+ }
+
+ protected PdfObject readSingleObject(int k) throws IOException {
+ strings.clear();
+ int k2 = k * 2;
+ int pos = xref[k2];
+ if (pos < 0)
+ return null;
+ if (xref[k2 + 1] > 0)
+ pos = objStmToOffset.get(xref[k2 + 1]);
+ tokens.seek(pos);
+ tokens.nextValidToken();
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ tokens.throwError("Invalid object number.");
+ objNum = tokens.intValue();
+ tokens.nextValidToken();
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ tokens.throwError("Invalid generation number.");
+ objGen = tokens.intValue();
+ tokens.nextValidToken();
+ if (!tokens.getStringValue().equals("obj"))
+ tokens.throwError("Token 'obj' expected.");
+ PdfObject obj;
+ try {
+ obj = readPRObject();
+ for (int j = 0; j < strings.size(); ++j) {
+ PdfString str = (PdfString)strings.get(j);
+ str.decrypt(this);
+ }
+ if (obj.isStream()) {
+ checkPRStreamLength((PRStream)obj);
+ }
+ }
+ catch (Exception e) {
+ obj = null;
+ }
+ if (xref[k2 + 1] > 0) {
+ obj = readOneObjStm((PRStream)obj, xref[k2]);
+ }
+ xrefObj.set(k, obj);
+ return obj;
+ }
+
+ protected PdfObject readOneObjStm(PRStream stream, int idx) throws IOException {
+ int first = ((PdfNumber)getPdfObject(stream.get(PdfName.FIRST))).intValue();
+ byte b[] = getStreamBytes(stream, tokens.getFile());
+ PRTokeniser saveTokens = tokens;
+ tokens = new PRTokeniser(b);
+ try {
+ int address = 0;
+ boolean ok = true;
+ ++idx;
+ for (int k = 0; k < idx; ++k) {
+ ok = tokens.nextToken();
+ if (!ok)
+ break;
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
+ ok = false;
+ break;
+ }
+ ok = tokens.nextToken();
+ if (!ok)
+ break;
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
+ ok = false;
+ break;
+ }
+ address = tokens.intValue() + first;
+ }
+ if (!ok)
+ throw new IOException("Error reading ObjStm");
+ tokens.seek(address);
+ return readPRObject();
+ }
+ finally {
+ tokens = saveTokens;
+ }
+ }
+
+ /**
+ * @return the percentage of the cross reference table that has been read
+ */
+ public double dumpPerc() {
+ int total = 0;
+ for (int k = 0; k < xrefObj.size(); ++k) {
+ if (xrefObj.get(k) != null)
+ ++total;
+ }
+ return (total * 100.0 / xrefObj.size());
+ }
+
+ protected void readDocObj() throws IOException {
+ ArrayList streams = new ArrayList();
+ xrefObj = new ArrayList(xref.length / 2);
+ xrefObj.addAll(Collections.nCopies(xref.length / 2, null));
+ for (int k = 2; k < xref.length; k += 2) {
+ int pos = xref[k];
+ if (pos <= 0 || xref[k + 1] > 0)
+ continue;
+ tokens.seek(pos);
+ tokens.nextValidToken();
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ tokens.throwError("Invalid object number.");
+ objNum = tokens.intValue();
+ tokens.nextValidToken();
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ tokens.throwError("Invalid generation number.");
+ objGen = tokens.intValue();
+ tokens.nextValidToken();
+ if (!tokens.getStringValue().equals("obj"))
+ tokens.throwError("Token 'obj' expected.");
+ PdfObject obj;
+ try {
+ obj = readPRObject();
+ if (obj.isStream()) {
+ streams.add(obj);
+ }
+ }
+ catch (Exception e) {
+ obj = null;
+ }
+ xrefObj.set(k / 2, obj);
+ }
+ for (int k = 0; k < streams.size(); ++k) {
+ checkPRStreamLength((PRStream)streams.get(k));
+ }
+ readDecryptedDocObj();
+ if (objStmMark != null) {
+ for (Iterator i = objStmMark.entrySet().iterator(); i.hasNext();) {
+ Map.Entry entry = (Map.Entry)i.next();
+ int n = ((Integer)entry.getKey()).intValue();
+ IntHashtable h = (IntHashtable)entry.getValue();
+ readObjStm((PRStream)xrefObj.get(n), h);
+ xrefObj.set(n, null);
+ }
+ objStmMark = null;
+ }
+ xref = null;
+ }
+
+ private void checkPRStreamLength(PRStream stream) throws IOException {
+ int fileLength = tokens.length();
+ int start = stream.getOffset();
+ boolean calc = false;
+ int streamLength = 0;
+ PdfObject obj = getPdfObjectRelease(stream.get(PdfName.LENGTH));
+ if (obj != null && obj.type() == PdfObject.NUMBER) {
+ streamLength = ((PdfNumber)obj).intValue();
+ if (streamLength + start > fileLength - 20)
+ calc = true;
+ else {
+ tokens.seek(start + streamLength);
+ String line = tokens.readString(20);
+ if (!line.startsWith("\nendstream") &&
+ !line.startsWith("\r\nendstream") &&
+ !line.startsWith("\rendstream") &&
+ !line.startsWith("endstream"))
+ calc = true;
+ }
+ }
+ else
+ calc = true;
+ if (calc) {
+ byte tline[] = new byte[16];
+ tokens.seek(start);
+ while (true) {
+ int pos = tokens.getFilePointer();
+ if (!tokens.readLineSegment(tline))
+ break;
+ if (equalsn(tline, endstream)) {
+ streamLength = pos - start;
+ break;
+ }
+ if (equalsn(tline, endobj)) {
+ tokens.seek(pos - 16);
+ String s = tokens.readString(16);
+ int index = s.indexOf("endstream");
+ if (index >= 0)
+ pos = pos - 16 + index;
+ streamLength = pos - start;
+ break;
+ }
+ }
+ }
+ stream.setLength(streamLength);
+ }
+
+ protected void readObjStm(PRStream stream, IntHashtable map) throws IOException {
+ int first = ((PdfNumber)getPdfObject(stream.get(PdfName.FIRST))).intValue();
+ int n = ((PdfNumber)getPdfObject(stream.get(PdfName.N))).intValue();
+ byte b[] = getStreamBytes(stream, tokens.getFile());
+ PRTokeniser saveTokens = tokens;
+ tokens = new PRTokeniser(b);
+ try {
+ int address[] = new int[n];
+ int objNumber[] = new int[n];
+ boolean ok = true;
+ for (int k = 0; k < n; ++k) {
+ ok = tokens.nextToken();
+ if (!ok)
+ break;
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
+ ok = false;
+ break;
+ }
+ objNumber[k] = tokens.intValue();
+ ok = tokens.nextToken();
+ if (!ok)
+ break;
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER) {
+ ok = false;
+ break;
+ }
+ address[k] = tokens.intValue() + first;
+ }
+ if (!ok)
+ throw new IOException("Error reading ObjStm");
+ for (int k = 0; k < n; ++k) {
+ if (map.containsKey(k)) {
+ tokens.seek(address[k]);
+ PdfObject obj = readPRObject();
+ xrefObj.set(objNumber[k], obj);
+ }
+ }
+ }
+ finally {
+ tokens = saveTokens;
+ }
+ }
+
+ /**
+ * Eliminates the reference to the object freeing the memory used by it and clearing
+ * the xref entry.
+ * @param obj the object. If it's an indirect reference it will be eliminated
+ * @return the object or the already erased dereferenced object
+ */
+ public static PdfObject killIndirect(PdfObject obj) {
+ if (obj == null || obj.isNull())
+ return null;
+ PdfObject ret = getPdfObjectRelease(obj);
+ if (obj.isIndirect()) {
+ PRIndirectReference ref = (PRIndirectReference)obj;
+ PdfReader reader = ref.getReader();
+ int n = ref.getNumber();
+ reader.xrefObj.set(n, null);
+ if (reader.partial)
+ reader.xref[n * 2] = -1;
+ }
+ return ret;
+ }
+
+ private void ensureXrefSize(int size) {
+ if (size == 0)
+ return;
+ if (xref == null)
+ xref = new int[size];
+ else {
+ if (xref.length < size) {
+ int xref2[] = new int[size];
+ System.arraycopy(xref, 0, xref2, 0, xref.length);
+ xref = xref2;
+ }
+ }
+ }
+
+ protected void readXref() throws IOException {
+ hybridXref = false;
+ newXrefType = false;
+ tokens.seek(tokens.getStartxref());
+ tokens.nextToken();
+ if (!tokens.getStringValue().equals("startxref"))
+ throw new IOException("startxref not found.");
+ tokens.nextToken();
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ throw new IOException("startxref is not followed by a number.");
+ int startxref = tokens.intValue();
+ lastXref = startxref;
+ eofPos = tokens.getFilePointer();
+ try {
+ if (readXRefStream(startxref)) {
+ newXrefType = true;
+ return;
+ }
+ }
+ catch (Exception e) {}
+ xref = null;
+ tokens.seek(startxref);
+ trailer = readXrefSection();
+ PdfDictionary trailer2 = trailer;
+ while (true) {
+ PdfNumber prev = (PdfNumber)trailer2.get(PdfName.PREV);
+ if (prev == null)
+ break;
+ tokens.seek(prev.intValue());
+ trailer2 = readXrefSection();
+ }
+ }
+
+ protected PdfDictionary readXrefSection() throws IOException {
+ tokens.nextValidToken();
+ if (!tokens.getStringValue().equals("xref"))
+ tokens.throwError("xref subsection not found");
+ int start = 0;
+ int end = 0;
+ int pos = 0;
+ int gen = 0;
+ while (true) {
+ tokens.nextValidToken();
+ if (tokens.getStringValue().equals("trailer"))
+ break;
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ tokens.throwError("Object number of the first object in this xref subsection not found");
+ start = tokens.intValue();
+ tokens.nextValidToken();
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ tokens.throwError("Number of entries in this xref subsection not found");
+ end = tokens.intValue() + start;
+ if (start == 1) { // fix incorrect start number
+ int back = tokens.getFilePointer();
+ tokens.nextValidToken();
+ pos = tokens.intValue();
+ tokens.nextValidToken();
+ gen = tokens.intValue();
+ if (pos == 0 && gen == 65535) {
+ --start;
+ --end;
+ }
+ tokens.seek(back);
+ }
+ ensureXrefSize(end * 2);
+ for (int k = start; k < end; ++k) {
+ tokens.nextValidToken();
+ pos = tokens.intValue();
+ tokens.nextValidToken();
+ gen = tokens.intValue();
+ tokens.nextValidToken();
+ int p = k * 2;
+ if (tokens.getStringValue().equals("n")) {
+ if (xref[p] == 0 && xref[p + 1] == 0) {
+// if (pos == 0)
+// tokens.throwError("File position 0 cross-reference entry in this xref subsection");
+ xref[p] = pos;
+ }
+ }
+ else if (tokens.getStringValue().equals("f")) {
+ if (xref[p] == 0 && xref[p + 1] == 0)
+ xref[p] = -1;
+ }
+ else
+ tokens.throwError("Invalid cross-reference entry in this xref subsection");
+ }
+ }
+ PdfDictionary trailer = (PdfDictionary)readPRObject();
+ PdfNumber xrefSize = (PdfNumber)trailer.get(PdfName.SIZE);
+ ensureXrefSize(xrefSize.intValue() * 2);
+ PdfObject xrs = trailer.get(PdfName.XREFSTM);
+ if (xrs != null && xrs.isNumber()) {
+ int loc = ((PdfNumber)xrs).intValue();
+ try {
+ readXRefStream(loc);
+ newXrefType = true;
+ hybridXref = true;
+ }
+ catch (IOException e) {
+ xref = null;
+ throw e;
+ }
+ }
+ return trailer;
+ }
+
+ protected boolean readXRefStream(int ptr) throws IOException {
+ tokens.seek(ptr);
+ int thisStream = 0;
+ if (!tokens.nextToken())
+ return false;
+ if (tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ return false;
+ thisStream = tokens.intValue();
+ if (!tokens.nextToken() || tokens.getTokenType() != PRTokeniser.TK_NUMBER)
+ return false;
+ if (!tokens.nextToken() || !tokens.getStringValue().equals("obj"))
+ return false;
+ PdfObject object = readPRObject();
+ PRStream stm = null;
+ if (object.isStream()) {
+ stm = (PRStream)object;
+ if (!PdfName.XREF.equals(stm.get(PdfName.TYPE)))
+ return false;
+ }
+ if (trailer == null) {
+ trailer = new PdfDictionary();
+ trailer.putAll(stm);
+ }
+ stm.setLength(((PdfNumber)stm.get(PdfName.LENGTH)).intValue());
+ int size = ((PdfNumber)stm.get(PdfName.SIZE)).intValue();
+ PdfArray index;
+ PdfObject obj = stm.get(PdfName.INDEX);
+ if (obj == null) {
+ index = new PdfArray();
+ index.add(new int[]{0, size});
+ }
+ else
+ index = (PdfArray)obj;
+ PdfArray w = (PdfArray)stm.get(PdfName.W);
+ int prev = -1;
+ obj = stm.get(PdfName.PREV);
+ if (obj != null)
+ prev = ((PdfNumber)obj).intValue();
+ // Each xref pair is a position
+ // type 0 -> -1, 0
+ // type 1 -> offset, 0
+ // type 2 -> index, obj num
+ ensureXrefSize(size * 2);
+ if (objStmMark == null && !partial)
+ objStmMark = new HashMap();
+ if (objStmToOffset == null && partial)
+ objStmToOffset = new IntHashtable();
+ byte b[] = getStreamBytes(stm, tokens.getFile());
+ int bptr = 0;
+ ArrayList wa = w.getArrayList();
+ int wc[] = new int[3];
+ for (int k = 0; k < 3; ++k)
+ wc[k] = ((PdfNumber)wa.get(k)).intValue();
+ ArrayList sections = index.getArrayList();
+ for (int idx = 0; idx < sections.size(); idx += 2) {
+ int start = ((PdfNumber)sections.get(idx)).intValue();
+ int length = ((PdfNumber)sections.get(idx + 1)).intValue();
+ ensureXrefSize((start + length) * 2);
+ while (length-- > 0) {
+ int type = 1;
+ if (wc[0] > 0) {
+ type = 0;
+ for (int k = 0; k < wc[0]; ++k)
+ type = (type << 8) + (b[bptr++] & 0xff);
+ }
+ int field2 = 0;
+ for (int k = 0; k < wc[1]; ++k)
+ field2 = (field2 << 8) + (b[bptr++] & 0xff);
+ int field3 = 0;
+ for (int k = 0; k < wc[2]; ++k)
+ field3 = (field3 << 8) + (b[bptr++] & 0xff);
+ int base = start * 2;
+ if (xref[base] == 0 && xref[base + 1] == 0) {
+ switch (type) {
+ case 0:
+ xref[base] = -1;
+ break;
+ case 1:
+ xref[base] = field2;
+ break;
+ case 2:
+ xref[base] = field3;
+ xref[base + 1] = field2;
+ if (partial) {
+ objStmToOffset.put(field2, 0);
+ }
+ else {
+ Integer on = new Integer(field2);
+ IntHashtable seq = (IntHashtable)objStmMark.get(on);
+ if (seq == null) {
+ seq = new IntHashtable();
+ seq.put(field3, 1);
+ objStmMark.put(on, seq);
+ }
+ else
+ seq.put(field3, 1);
+ }
+ break;
+ }
+ }
+ ++start;
+ }
+ }
+ thisStream *= 2;
+ if (thisStream < xref.length)
+ xref[thisStream] = -1;
+
+ if (prev == -1)
+ return true;
+ return readXRefStream(prev);
+ }
+
+ protected void rebuildXref() throws IOException {
+ hybridXref = false;
+ newXrefType = false;
+ tokens.seek(0);
+ int xr[][] = new int[1024][];
+ int top = 0;
+ trailer = null;
+ byte line[] = new byte[64];
+ for (;;) {
+ int pos = tokens.getFilePointer();
+ if (!tokens.readLineSegment(line))
+ break;
+ if (line[0] == 't') {
+ if (!PdfEncodings.convertToString(line, null).startsWith("trailer"))
+ continue;
+ tokens.seek(pos);
+ tokens.nextToken();
+ pos = tokens.getFilePointer();
+ try {
+ PdfDictionary dic = (PdfDictionary)readPRObject();
+ if (dic.get(PdfName.ROOT) != null)
+ trailer = dic;
+ else
+ tokens.seek(pos);
+ }
+ catch (Exception e) {
+ tokens.seek(pos);
+ }
+ }
+ else if (line[0] >= '0' && line[0] <= '9') {
+ int obj[] = PRTokeniser.checkObjectStart(line);
+ if (obj == null)
+ continue;
+ int num = obj[0];
+ int gen = obj[1];
+ if (num >= xr.length) {
+ int newLength = num * 2;
+ int xr2[][] = new int[newLength][];
+ System.arraycopy(xr, 0, xr2, 0, top);
+ xr = xr2;
+ }
+ if (num >= top)
+ top = num + 1;
+ if (xr[num] == null || gen >= xr[num][1]) {
+ obj[0] = pos;
+ xr[num] = obj;
+ }
+ }
+ }
+ if (trailer == null)
+ throw new IOException("trailer not found.");
+ xref = new int[top * 2];
+ for (int k = 0; k < top; ++k) {
+ int obj[] = xr[k];
+ if (obj != null)
+ xref[k * 2] = obj[0];
+ }
+ }
+
+ protected PdfDictionary readDictionary() throws IOException {
+ PdfDictionary dic = new PdfDictionary();
+ while (true) {
+ tokens.nextValidToken();
+ if (tokens.getTokenType() == PRTokeniser.TK_END_DIC)
+ break;
+ if (tokens.getTokenType() != PRTokeniser.TK_NAME)
+ tokens.throwError("Dictionary key is not a name.");
+ PdfName name = new PdfName(tokens.getStringValue(), false);
+ PdfObject obj = readPRObject();
+ int type = obj.type();
+ if (-type == PRTokeniser.TK_END_DIC)
+ tokens.throwError("Unexpected '>>'");
+ if (-type == PRTokeniser.TK_END_ARRAY)
+ tokens.throwError("Unexpected ']'");
+ dic.put(name, obj);
+ }
+ return dic;
+ }
+
+ protected PdfArray readArray() throws IOException {
+ PdfArray array = new PdfArray();
+ while (true) {
+ PdfObject obj = readPRObject();
+ int type = obj.type();
+ if (-type == PRTokeniser.TK_END_ARRAY)
+ break;
+ if (-type == PRTokeniser.TK_END_DIC)
+ tokens.throwError("Unexpected '>>'");
+ array.add(obj);
+ }
+ return array;
+ }
+
+ protected PdfObject readPRObject() throws IOException {
+ tokens.nextValidToken();
+ int type = tokens.getTokenType();
+ switch (type) {
+ case PRTokeniser.TK_START_DIC: {
+ PdfDictionary dic = readDictionary();
+ int pos = tokens.getFilePointer();
+ // be careful in the trailer. May not be a "next" token.
+ if (tokens.nextToken() && tokens.getStringValue().equals("stream")) {
+ int ch = tokens.read();
+ if (ch != '\n')
+ ch = tokens.read();
+ if (ch != '\n')
+ tokens.backOnePosition(ch);
+ PRStream stream = new PRStream(this, tokens.getFilePointer());
+ stream.putAll(dic);
+ stream.setObjNum(objNum, objGen);
+ return stream;
+ }
+ else {
+ tokens.seek(pos);
+ return dic;
+ }
+ }
+ case PRTokeniser.TK_START_ARRAY:
+ return readArray();
+ case PRTokeniser.TK_NUMBER:
+ return new PdfNumber(tokens.getStringValue());
+ case PRTokeniser.TK_STRING:
+ PdfString str = new PdfString(tokens.getStringValue(), null).setHexWriting(tokens.isHexString());
+ str.setObjNum(objNum, objGen);
+ if (strings != null)
+ strings.add(str);
+ return str;
+ case PRTokeniser.TK_NAME:
+ return new PdfName(tokens.getStringValue(), false);
+ case PRTokeniser.TK_REF:
+ int num = tokens.getReference();
+ PRIndirectReference ref = new PRIndirectReference(this, num, tokens.getGeneration());
+ if (visited != null && !visited[num]) {
+ visited[num] = true;
+ newHits.put(num, 1);
+ }
+ return ref;
+ default:
+ return new PdfLiteral(-type, tokens.getStringValue());
+ }
+ }
+
+ /** Decodes a stream that has the FlateDecode filter.
+ * @param in the input data
+ * @return the decoded data
+ */
+ public static byte[] FlateDecode(byte in[]) {
+ byte b[] = FlateDecode(in, true);
+ if (b == null)
+ return FlateDecode(in, false);
+ return b;
+ }
+
+ /**
+ * @param in
+ * @param dicPar
+ * @return a byte array
+ */
+ public static byte[] decodePredictor(byte in[], PdfObject dicPar) {
+ if (dicPar == null || !dicPar.isDictionary())
+ return in;
+ PdfDictionary dic = (PdfDictionary)dicPar;
+ PdfObject obj = getPdfObject(dic.get(PdfName.PREDICTOR));
+ if (obj == null || !obj.isNumber())
+ return in;
+ int predictor = ((PdfNumber)obj).intValue();
+ if (predictor < 10)
+ return in;
+ int width = 1;
+ obj = getPdfObject(dic.get(PdfName.COLUMNS));
+ if (obj != null && obj.isNumber())
+ width = ((PdfNumber)obj).intValue();
+ int colors = 1;
+ obj = getPdfObject(dic.get(PdfName.COLORS));
+ if (obj != null && obj.isNumber())
+ colors = ((PdfNumber)obj).intValue();
+ int bpc = 8;
+ obj = getPdfObject(dic.get(PdfName.BITSPERCOMPONENT));
+ if (obj != null && obj.isNumber())
+ bpc = ((PdfNumber)obj).intValue();
+ DataInputStream dataStream = new DataInputStream(new ByteArrayInputStream(in));
+ ByteArrayOutputStream fout = new ByteArrayOutputStream(in.length);
+ int bytesPerPixel = colors * bpc / 8;
+ int bytesPerRow = (colors*width*bpc + 7)/8;
+ byte[] curr = new byte[bytesPerRow];
+ byte[] prior = new byte[bytesPerRow];
+
+ // Decode the (sub)image row-by-row
+ while (true) {
+ // Read the filter type byte and a row of data
+ int filter = 0;
+ try {
+ filter = dataStream.read();
+ if (filter < 0) {
+ return fout.toByteArray();
+ }
+ dataStream.readFully(curr, 0, bytesPerRow);
+ } catch (Exception e) {
+ return fout.toByteArray();
+ }
+
+ switch (filter) {
+ case 0: //PNG_FILTER_NONE
+ break;
+ case 1: //PNG_FILTER_SUB
+ for (int i = bytesPerPixel; i < bytesPerRow; i++) {
+ curr[i] += curr[i - bytesPerPixel];
+ }
+ break;
+ case 2: //PNG_FILTER_UP
+ for (int i = 0; i < bytesPerRow; i++) {
+ curr[i] += prior[i];
+ }
+ break;
+ case 3: //PNG_FILTER_AVERAGE
+ for (int i = 0; i < bytesPerPixel; i++) {
+ curr[i] += prior[i] / 2;
+ }
+ for (int i = bytesPerPixel; i < bytesPerRow; i++) {
+ curr[i] += ((curr[i - bytesPerPixel] & 0xff) + (prior[i] & 0xff))/2;
+ }
+ break;
+ case 4: //PNG_FILTER_PAETH
+ for (int i = 0; i < bytesPerPixel; i++) {
+ curr[i] += prior[i];
+ }
+
+ for (int i = bytesPerPixel; i < bytesPerRow; i++) {
+ int a = curr[i - bytesPerPixel] & 0xff;
+ int b = prior[i] & 0xff;
+ int c = prior[i - bytesPerPixel] & 0xff;
+
+ int p = a + b - c;
+ int pa = Math.abs(p - a);
+ int pb = Math.abs(p - b);
+ int pc = Math.abs(p - c);
+
+ int ret;
+
+ if ((pa <= pb) && (pa <= pc)) {
+ ret = a;
+ } else if (pb <= pc) {
+ ret = b;
+ } else {
+ ret = c;
+ }
+ curr[i] += (byte)(ret);
+ }
+ break;
+ default:
+ // Error -- uknown filter type
+ throw new RuntimeException("PNG filter unknown.");
+ }
+ try {
+ fout.write(curr);
+ }
+ catch (IOException ioe) {
+ // Never happens
+ }
+
+ // Swap curr and prior
+ byte[] tmp = prior;
+ prior = curr;
+ curr = tmp;
+ }
+ }
+
+ /** A helper to FlateDecode.
+ * @param in the input data
+ * @param strict <CODE>true</CODE> to read a correct stream. <CODE>false</CODE>
+ * to try to read a corrupted stream
+ * @return the decoded data
+ */
+ public static byte[] FlateDecode(byte in[], boolean strict) {
+ ByteArrayInputStream stream = new ByteArrayInputStream(in);
+ InflaterInputStream zip = new InflaterInputStream(stream);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ byte b[] = new byte[strict ? 4092 : 1];
+ try {
+ int n;
+ while ((n = zip.read(b)) >= 0) {
+ out.write(b, 0, n);
+ }
+ zip.close();
+ out.close();
+ return out.toByteArray();
+ }
+ catch (Exception e) {
+ if (strict)
+ return null;
+ return out.toByteArray();
+ }
+ }
+
+ /** Decodes a stream that has the ASCIIHexDecode filter.
+ * @param in the input data
+ * @return the decoded data
+ */
+ public static byte[] ASCIIHexDecode(byte in[]) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ boolean first = true;
+ int n1 = 0;
+ for (int k = 0; k < in.length; ++k) {
+ int ch = in[k] & 0xff;
+ if (ch == '>')
+ break;
+ if (PRTokeniser.isWhitespace(ch))
+ continue;
+ int n = PRTokeniser.getHex(ch);
+ if (n == -1)
+ throw new RuntimeException("Illegal character in ASCIIHexDecode.");
+ if (first)
+ n1 = n;
+ else
+ out.write((byte)((n1 << 4) + n));
+ first = !first;
+ }
+ if (!first)
+ out.write((byte)(n1 << 4));
+ return out.toByteArray();
+ }
+
+ /** Decodes a stream that has the ASCII85Decode filter.
+ * @param in the input data
+ * @return the decoded data
+ */
+ public static byte[] ASCII85Decode(byte in[]) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int state = 0;
+ int chn[] = new int[5];
+ for (int k = 0; k < in.length; ++k) {
+ int ch = in[k] & 0xff;
+ if (ch == '~')
+ break;
+ if (PRTokeniser.isWhitespace(ch))
+ continue;
+ if (ch == 'z' && state == 0) {
+ out.write(0);
+ out.write(0);
+ out.write(0);
+ out.write(0);
+ continue;
+ }
+ if (ch < '!' || ch > 'u')
+ throw new RuntimeException("Illegal character in ASCII85Decode.");
+ chn[state] = ch - '!';
+ ++state;
+ if (state == 5) {
+ state = 0;
+ int r = 0;
+ for (int j = 0; j < 5; ++j)
+ r = r * 85 + chn[j];
+ out.write((byte)(r >> 24));
+ out.write((byte)(r >> 16));
+ out.write((byte)(r >> 8));
+ out.write((byte)r);
+ }
+ }
+ int r = 0;
+ if (state == 1)
+ throw new RuntimeException("Illegal length in ASCII85Decode.");
+ if (state == 2) {
+ r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85;
+ out.write((byte)(r >> 24));
+ }
+ else if (state == 3) {
+ r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + chn[2] * 85 * 85;
+ out.write((byte)(r >> 24));
+ out.write((byte)(r >> 16));
+ }
+ else if (state == 4) {
+ r = chn[0] * 85 * 85 * 85 * 85 + chn[1] * 85 * 85 * 85 + chn[2] * 85 * 85 + chn[3] * 85 ;
+ out.write((byte)(r >> 24));
+ out.write((byte)(r >> 16));
+ out.write((byte)(r >> 8));
+ }
+ return out.toByteArray();
+ }
+
+ /** Decodes a stream that has the LZWDecode filter.
+ * @param in the input data
+ * @return the decoded data
+ */
+ public static byte[] LZWDecode(byte in[]) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ LZWDecoder lzw = new LZWDecoder();
+ lzw.decode(in, out);
+ return out.toByteArray();
+ }
+
+ /** Checks if the document had errors and was rebuilt.
+ * @return true if rebuilt.
+ *
+ */
+ public boolean isRebuilt() {
+ return this.rebuilt;
+ }
+
+ /** Gets the dictionary that represents a page.
+ * @param pageNum the page number. 1 is the first
+ * @return the page dictionary
+ */
+ public PdfDictionary getPageN(int pageNum) {
+ PdfDictionary dic = pageRefs.getPageN(pageNum);
+ if (dic == null)
+ return null;
+ if (appendable)
+ dic.setIndRef(pageRefs.getPageOrigRef(pageNum));
+ return dic;
+ }
+
+ /**
+ * @param pageNum
+ * @return a Dictionary object
+ */
+ public PdfDictionary getPageNRelease(int pageNum) {
+ PdfDictionary dic = getPageN(pageNum);
+ pageRefs.releasePage(pageNum);
+ return dic;
+ }
+
+ /**
+ * @param pageNum
+ */
+ public void releasePage(int pageNum) {
+ pageRefs.releasePage(pageNum);
+ }
+
+ /**
+ *
+ */
+ public void resetReleasePage() {
+ pageRefs.resetReleasePage();
+ }
+
+ /** Gets the page reference to this page.
+ * @param pageNum the page number. 1 is the first
+ * @return the page reference
+ */
+ public PRIndirectReference getPageOrigRef(int pageNum) {
+ return pageRefs.getPageOrigRef(pageNum);
+ }
+
+ /** Gets the contents of the page.
+ * @param pageNum the page number. 1 is the first
+ * @param file the location of the PDF document
+ * @throws IOException on error
+ * @return the content
+ */
+ public byte[] getPageContent(int pageNum, RandomAccessFileOrArray file) throws IOException{
+ PdfDictionary page = getPageNRelease(pageNum);
+ if (page == null)
+ return null;
+ PdfObject contents = getPdfObjectRelease(page.get(PdfName.CONTENTS));
+ if (contents == null)
+ return new byte[0];
+ ByteArrayOutputStream bout = null;
+ if (contents.isStream()) {
+ return getStreamBytes((PRStream)contents, file);
+ }
+ else if (contents.isArray()) {
+ PdfArray array = (PdfArray)contents;
+ ArrayList list = array.getArrayList();
+ bout = new ByteArrayOutputStream();
+ for (int k = 0; k < list.size(); ++k) {
+ PdfObject item = getPdfObjectRelease((PdfObject)list.get(k));
+ if (item == null || !item.isStream())
+ continue;
+ byte[] b = getStreamBytes((PRStream)item, file);
+ bout.write(b);
+ if (k != list.size() - 1)
+ bout.write('\n');
+ }
+ return bout.toByteArray();
+ }
+ else
+ return new byte[0];
+ }
+
+ /** Gets the contents of the page.
+ * @param pageNum the page number. 1 is the first
+ * @throws IOException on error
+ * @return the content
+ */
+ public byte[] getPageContent(int pageNum) throws IOException{
+ RandomAccessFileOrArray rf = getSafeFile();
+ try {
+ rf.reOpen();
+ return getPageContent(pageNum, rf);
+ }
+ finally {
+ try{rf.close();}catch(Exception e){}
+ }
+ }
+
+ protected void killXref(PdfObject obj) {
+ if (obj == null)
+ return;
+ if ((obj instanceof PdfIndirectReference) && !obj.isIndirect())
+ return;
+ switch (obj.type()) {
+ case PdfObject.INDIRECT: {
+ int xr = ((PRIndirectReference)obj).getNumber();
+ obj = (PdfObject)xrefObj.get(xr);
+ xrefObj.set(xr, null);
+ freeXref = xr;
+ killXref(obj);
+ break;
+ }
+ case PdfObject.ARRAY: {
+ ArrayList t = ((PdfArray)obj).getArrayList();
+ for (int i = 0; i < t.size(); ++i)
+ killXref((PdfObject)t.get(i));
+ break;
+ }
+ case PdfObject.STREAM:
+ case PdfObject.DICTIONARY: {
+ PdfDictionary dic = (PdfDictionary)obj;
+ for (Iterator i = dic.getKeys().iterator(); i.hasNext();){
+ killXref(dic.get((PdfName)i.next()));
+ }
+ break;
+ }
+ }
+ }
+
+ /** Sets the contents of the page.
+ * @param content the new page content
+ * @param pageNum the page number. 1 is the first
+ * @throws IOException on error
+ */
+ public void setPageContent(int pageNum, byte content[]) throws IOException{
+ PdfDictionary page = getPageN(pageNum);
+ if (page == null)
+ return;
+ PdfObject contents = page.get(PdfName.CONTENTS);
+ freeXref = -1;
+ killXref(contents);
+ if (freeXref == -1) {
+ xrefObj.add(null);
+ freeXref = xrefObj.size() - 1;
+ }
+ page.put(PdfName.CONTENTS, new PRIndirectReference(this, freeXref));
+ xrefObj.set(freeXref, new PRStream(this, content));
+ }
+
+ /** Get the content from a stream applying the required filters.
+ * @param stream the stream
+ * @param file the location where the stream is
+ * @throws IOException on error
+ * @return the stream content
+ */
+ public static byte[] getStreamBytes(PRStream stream, RandomAccessFileOrArray file) throws IOException {
+ PdfObject filter = getPdfObjectRelease(stream.get(PdfName.FILTER));
+ byte[] b = getStreamBytesRaw(stream, file);
+ ArrayList filters = new ArrayList();
+ if (filter != null) {
+ if (filter.isName())
+ filters.add(filter);
+ else if (filter.isArray())
+ filters = ((PdfArray)filter).getArrayList();
+ }
+ ArrayList dp = new ArrayList();
+ PdfObject dpo = getPdfObjectRelease(stream.get(PdfName.DECODEPARMS));
+ if (dpo == null || (!dpo.isDictionary() && !dpo.isArray()))
+ dpo = getPdfObjectRelease(stream.get(PdfName.DP));
+ if (dpo != null) {
+ if (dpo.isDictionary())
+ dp.add(dpo);
+ else if (dpo.isArray())
+ dp = ((PdfArray)dpo).getArrayList();
+ }
+ String name;
+ for (int j = 0; j < filters.size(); ++j) {
+ name = ((PdfName)PdfReader.getPdfObjectRelease((PdfObject)filters.get(j))).toString();
+ if (name.equals("/FlateDecode") || name.equals("/Fl")) {
+ b = PdfReader.FlateDecode(b);
+ PdfObject dicParam = null;
+ if (j < dp.size()) {
+ dicParam = (PdfObject)dp.get(j);
+ b = decodePredictor(b, dicParam);
+ }
+ }
+ else if (name.equals("/ASCIIHexDecode") || name.equals("/AHx"))
+ b = PdfReader.ASCIIHexDecode(b);
+ else if (name.equals("/ASCII85Decode") || name.equals("/A85"))
+ b = PdfReader.ASCII85Decode(b);
+ else if (name.equals("/LZWDecode")) {
+ b = PdfReader.LZWDecode(b);
+ PdfObject dicParam = null;
+ if (j < dp.size()) {
+ dicParam = (PdfObject)dp.get(j);
+ b = decodePredictor(b, dicParam);
+ }
+ }
+ else
+ throw new IOException("The filter " + name + " is not supported.");
+ }
+ return b;
+ }
+
+ /** Get the content from a stream applying the required filters.
+ * @param stream the stream
+ * @throws IOException on error
+ * @return the stream content
+ */
+ public static byte[] getStreamBytes(PRStream stream) throws IOException {
+ RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
+ try {
+ rf.reOpen();
+ return PdfReader.getStreamBytes(stream, rf);
+ }
+ finally {
+ try{rf.close();}catch(Exception e){}
+ }
+ }
+
+ /** Get the content from a stream as it is without applying any filter.
+ * @param stream the stream
+ * @param file the location where the stream is
+ * @throws IOException on error
+ * @return the stream content
+ */
+ public static byte[] getStreamBytesRaw(PRStream stream, RandomAccessFileOrArray file) throws IOException {
+ PdfReader reader = stream.getReader();
+ byte b[];
+ if (stream.getOffset() < 0)
+ b = stream.getBytes();
+ else {
+ b = new byte[stream.getLength()];
+ file.seek(stream.getOffset());
+ file.readFully(b);
+ PdfEncryption decrypt = reader.getDecrypt();
+ if (decrypt != null) {
+ decrypt.setHashKey(stream.getObjNum(), stream.getObjGen());
+ decrypt.prepareKey();
+ decrypt.encryptRC4(b);
+ }
+ }
+ return b;
+ }
+
+ /** Get the content from a stream as it is without applying any filter.
+ * @param stream the stream
+ * @throws IOException on error
+ * @return the stream content
+ */
+ public static byte[] getStreamBytesRaw(PRStream stream) throws IOException {
+ RandomAccessFileOrArray rf = stream.getReader().getSafeFile();
+ try {
+ rf.reOpen();
+ return PdfReader.getStreamBytesRaw(stream, rf);
+ }
+ finally {
+ try{rf.close();}catch(Exception e){}
+ }
+ }
+
+ /** Eliminates shared streams if they exist. */
+ public void eliminateSharedStreams() {
+ if (!sharedStreams)
+ return;
+ sharedStreams = false;
+ if (pageRefs.size() == 1)
+ return;
+ ArrayList newRefs = new ArrayList();
+ ArrayList newStreams = new ArrayList();
+ IntHashtable visited = new IntHashtable();
+ for (int k = 1; k <= pageRefs.size(); ++k) {
+ PdfDictionary page = pageRefs.getPageN(k);
+ if (page == null)
+ continue;
+ PdfObject contents = getPdfObject(page.get(PdfName.CONTENTS));
+ if (contents == null)
+ continue;
+ if (contents.isStream()) {
+ PRIndirectReference ref = (PRIndirectReference)page.get(PdfName.CONTENTS);
+ if (visited.containsKey(ref.getNumber())) {
+ // need to duplicate
+ newRefs.add(ref);
+ newStreams.add(new PRStream((PRStream)contents, null));
+ }
+ else
+ visited.put(ref.getNumber(), 1);
+ }
+ else if (contents.isArray()) {
+ PdfArray array = (PdfArray)contents;
+ ArrayList list = array.getArrayList();
+ for (int j = 0; j < list.size(); ++j) {
+ PRIndirectReference ref = (PRIndirectReference)list.get(j);
+ if (visited.containsKey(ref.getNumber())) {
+ // need to duplicate
+ newRefs.add(ref);
+ newStreams.add(new PRStream((PRStream)getPdfObject(ref), null));
+ }
+ else
+ visited.put(ref.getNumber(), 1);
+ }
+ }
+ }
+ if (newStreams.size() == 0)
+ return;
+ for (int k = 0; k < newStreams.size(); ++k) {
+ xrefObj.add(newStreams.get(k));
+ PRIndirectReference ref = (PRIndirectReference)newRefs.get(k);
+ ref.setNumber(xrefObj.size() - 1, 0);
+ }
+ }
+
+ /** Checks if the document was changed.
+ * @return <CODE>true</CODE> if the document was changed,
+ * <CODE>false</CODE> otherwise
+ */
+ public boolean isTampered() {
+ return tampered;
+ }
+
+ /**
+ * Sets the tampered state. A tampered PdfReader cannot be reused in PdfStamper.
+ * @param tampered the tampered state
+ */
+ public void setTampered(boolean tampered) {
+ this.tampered = tampered;
+ }
+
+ /** Gets the XML metadata.
+ * @throws IOException on error
+ * @return the XML metadata
+ */
+ public byte[] getMetadata() throws IOException {
+ PdfObject obj = getPdfObject(catalog.get(PdfName.METADATA));
+ if (!(obj instanceof PRStream))
+ return null;
+ RandomAccessFileOrArray rf = getSafeFile();
+ byte b[] = null;
+ try {
+ rf.reOpen();
+ b = getStreamBytes((PRStream)obj, rf);
+ }
+ finally {
+ try {
+ rf.close();
+ }
+ catch (Exception e) {
+ // empty on purpose
+ }
+ }
+ return b;
+ }
+
+ /**
+ * Gets the byte address of the last xref table.
+ * @return the byte address of the last xref table
+ */
+ public int getLastXref() {
+ return lastXref;
+ }
+
+ /**
+ * Gets the number of xref objects.
+ * @return the number of xref objects
+ */
+ public int getXrefSize() {
+ return xrefObj.size();
+ }
+
+ /**
+ * Gets the byte address of the %%EOF marker.
+ * @return the byte address of the %%EOF marker
+ */
+ public int getEofPos() {
+ return eofPos;
+ }
+
+ /**
+ * Gets the PDF version. Only the last version char is returned. For example
+ * version 1.4 is returned as '4'.
+ * @return the PDF version
+ */
+ public char getPdfVersion() {
+ return pdfVersion;
+ }
+
+ /**
+ * Returns <CODE>true</CODE> if the PDF is encrypted.
+ * @return <CODE>true</CODE> if the PDF is encrypted
+ */
+ public boolean isEncrypted() {
+ return encrypted;
+ }
+
+ /**
+ * Gets the encryption permissions. It can be used directly in
+ * <CODE>PdfWriter.setEncryption()</CODE>.
+ * @return the encryption permissions
+ */
+ public int getPermissions() {
+ return pValue;
+ }
+
+ /**
+ * Returns <CODE>true</CODE> if the PDF has a 128 bit key encryption.
+ * @return <CODE>true</CODE> if the PDF has a 128 bit key encryption
+ */
+ public boolean is128Key() {
+ return rValue == 3;
+ }
+
+ /**
+ * Gets the trailer dictionary
+ * @return the trailer dictionary
+ */
+ public PdfDictionary getTrailer() {
+ return trailer;
+ }
+
+ PdfEncryption getDecrypt() {
+ return decrypt;
+ }
+
+ static boolean equalsn(byte a1[], byte a2[]) {
+ int length = a2.length;
+ for (int k = 0; k < length; ++k) {
+ if (a1[k] != a2[k])
+ return false;
+ }
+ return true;
+ }
+
+ static boolean existsName(PdfDictionary dic, PdfName key, PdfName value) {
+ PdfObject type = getPdfObjectRelease(dic.get(key));
+ if (type == null || !type.isName())
+ return false;
+ PdfName name = (PdfName)type;
+ return name.equals(value);
+ }
+
+ static String getFontName(PdfDictionary dic) {
+ PdfObject type = getPdfObjectRelease(dic.get(PdfName.BASEFONT));
+ if (type == null || !type.isName())
+ return null;
+ return PdfName.decodeName(type.toString());
+ }
+
+ static String getSubsetPrefix(PdfDictionary dic) {
+ String s = getFontName(dic);
+ if (s == null)
+ return null;
+ if (s.length() < 8 || s.charAt(6) != '+')
+ return null;
+ for (int k = 0; k < 6; ++k) {
+ char c = s.charAt(k);
+ if (c < 'A' || c > 'Z')
+ return null;
+ }
+ return s;
+ }
+
+ /** Finds all the font subsets and changes the prefixes to some
+ * random values.
+ * @return the number of font subsets altered
+ */
+ public int shuffleSubsetNames() {
+ int total = 0;
+ for (int k = 1; k < xrefObj.size(); ++k) {
+ PdfObject obj = getPdfObjectRelease(k);
+ if (obj == null || !obj.isDictionary())
+ continue;
+ PdfDictionary dic = (PdfDictionary)obj;
+ if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
+ continue;
+ if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
+ || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
+ || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) {
+ String s = getSubsetPrefix(dic);
+ if (s == null)
+ continue;
+ String ns = BaseFont.createSubsetPrefix() + s.substring(7);
+ PdfName newName = new PdfName(ns);
+ dic.put(PdfName.BASEFONT, newName);
+ setXrefPartialObject(k, dic);
+ ++total;
+ PdfDictionary fd = (PdfDictionary)getPdfObject(dic.get(PdfName.FONTDESCRIPTOR));
+ if (fd == null)
+ continue;
+ fd.put(PdfName.FONTNAME, newName);
+ }
+ else if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE0)) {
+ String s = getSubsetPrefix(dic);
+ PdfArray arr = (PdfArray)getPdfObject(dic.get(PdfName.DESCENDANTFONTS));
+ if (arr == null)
+ continue;
+ ArrayList list = arr.getArrayList();
+ if (list.size() == 0)
+ continue;
+ PdfDictionary desc = (PdfDictionary)getPdfObject((PdfObject)list.get(0));
+ String sde = getSubsetPrefix(desc);
+ if (sde == null)
+ continue;
+ String ns = BaseFont.createSubsetPrefix();
+ if (s != null)
+ dic.put(PdfName.BASEFONT, new PdfName(ns + s.substring(7)));
+ setXrefPartialObject(k, dic);
+ PdfName newName = new PdfName(ns + sde.substring(7));
+ desc.put(PdfName.BASEFONT, newName);
+ ++total;
+ PdfDictionary fd = (PdfDictionary)getPdfObject(desc.get(PdfName.FONTDESCRIPTOR));
+ if (fd == null)
+ continue;
+ fd.put(PdfName.FONTNAME, newName);
+ }
+ }
+ return total;
+ }
+
+ /** Finds all the fonts not subset but embedded and marks them as subset.
+ * @return the number of fonts altered
+ */
+ public int createFakeFontSubsets() {
+ int total = 0;
+ for (int k = 1; k < xrefObj.size(); ++k) {
+ PdfObject obj = getPdfObjectRelease(k);
+ if (obj == null || !obj.isDictionary())
+ continue;
+ PdfDictionary dic = (PdfDictionary)obj;
+ if (!existsName(dic, PdfName.TYPE, PdfName.FONT))
+ continue;
+ if (existsName(dic, PdfName.SUBTYPE, PdfName.TYPE1)
+ || existsName(dic, PdfName.SUBTYPE, PdfName.MMTYPE1)
+ || existsName(dic, PdfName.SUBTYPE, PdfName.TRUETYPE)) {
+ String s = getSubsetPrefix(dic);
+ if (s != null)
+ continue;
+ s = getFontName(dic);
+ if (s == null)
+ continue;
+ String ns = BaseFont.createSubsetPrefix() + s;
+ PdfDictionary fd = (PdfDictionary)getPdfObjectRelease(dic.get(PdfName.FONTDESCRIPTOR));
+ if (fd == null)
+ continue;
+ if (fd.get(PdfName.FONTFILE) == null && fd.get(PdfName.FONTFILE2) == null
+ && fd.get(PdfName.FONTFILE3) == null)
+ continue;
+ fd = (PdfDictionary)getPdfObject(dic.get(PdfName.FONTDESCRIPTOR));
+ PdfName newName = new PdfName(ns);
+ dic.put(PdfName.BASEFONT, newName);
+ fd.put(PdfName.FONTNAME, newName);
+ setXrefPartialObject(k, dic);
+ ++total;
+ }
+ }
+ return total;
+ }
+
+ private static PdfArray getNameArray(PdfObject obj) {
+ if (obj == null)
+ return null;
+ obj = getPdfObjectRelease(obj);
+ if (obj.isArray())
+ return (PdfArray)obj;
+ else if (obj.isDictionary()) {
+ PdfObject arr2 = getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.D));
+ if (arr2 != null && arr2.isArray())
+ return (PdfArray)arr2;
+ }
+ return null;
+ }
+
+ /**
+ * Gets all the named destinations as an <CODE>HashMap</CODE>. The key is the name
+ * and the value is the destinations array.
+ * @return gets all the named destinations
+ */
+ public HashMap getNamedDestination() {
+ HashMap names = getNamedDestinationFromNames();
+ names.putAll(getNamedDestinationFromStrings());
+ return names;
+ }
+
+ /**
+ * Gets the named destinations from the /Dests key in the catalog as an <CODE>HashMap</CODE>. The key is the name
+ * and the value is the destinations array.
+ * @return gets the named destinations
+ */
+ public HashMap getNamedDestinationFromNames() {
+ HashMap names = new HashMap();
+ if (catalog.get(PdfName.DESTS) != null) {
+ PdfDictionary dic = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.DESTS));
+ Set keys = dic.getKeys();
+ for (Iterator it = keys.iterator(); it.hasNext();) {
+ PdfName key = (PdfName)it.next();
+ String name = PdfName.decodeName(key.toString());
+ PdfArray arr = getNameArray(dic.get(key));
+ if (arr != null)
+ names.put(name, arr);
+ }
+ }
+ return names;
+ }
+
+ /**
+ * Gets the named destinations from the /Names key in the catalog as an <CODE>HashMap</CODE>. The key is the name
+ * and the value is the destinations array.
+ * @return gets the named destinations
+ */
+ public HashMap getNamedDestinationFromStrings() {
+ if (catalog.get(PdfName.NAMES) != null) {
+ PdfDictionary dic = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.NAMES));
+ dic = (PdfDictionary)getPdfObjectRelease(dic.get(PdfName.DESTS));
+ if (dic != null) {
+ HashMap names = PdfNameTree.readTree(dic);
+ for (Iterator it = names.entrySet().iterator(); it.hasNext();) {
+ Map.Entry entry = (Map.Entry)it.next();
+ PdfArray arr = getNameArray((PdfObject)entry.getValue());
+ if (arr != null)
+ entry.setValue(arr);
+ else
+ it.remove();
+ }
+ return names;
+ }
+ }
+ return new HashMap();
+ }
+
+ private boolean replaceNamedDestination(PdfObject obj, HashMap names) {
+ obj = getPdfObject(obj);
+ int objIdx = lastXrefPartial;
+ releaseLastXrefPartial();
+ if (obj != null && obj.isDictionary()) {
+ PdfObject ob2 = getPdfObjectRelease(((PdfDictionary)obj).get(PdfName.DEST));
+ String name = null;
+ if (ob2 != null) {
+ if (ob2.isName())
+ name = PdfName.decodeName(ob2.toString());
+ else if (ob2.isString())
+ name = ob2.toString();
+ PdfArray dest = (PdfArray)names.get(name);
+ if (dest != null) {
+ ((PdfDictionary)obj).put(PdfName.DEST, dest);
+ setXrefPartialObject(objIdx, obj);
+ return true;
+ }
+ }
+ else if ((ob2 = getPdfObject(((PdfDictionary)obj).get(PdfName.A))) != null) {
+ int obj2Idx = lastXrefPartial;
+ releaseLastXrefPartial();
+ PdfDictionary dic = (PdfDictionary)ob2;
+ PdfName type = (PdfName)getPdfObjectRelease(dic.get(PdfName.S));
+ if (PdfName.GOTO.equals(type)) {
+ PdfObject ob3 = getPdfObjectRelease(dic.get(PdfName.D));
+ if (ob3.isName())
+ name = PdfName.decodeName(ob3.toString());
+ else if (ob3.isString())
+ name = ob3.toString();
+ PdfArray dest = (PdfArray)names.get(name);
+ if (dest != null) {
+ dic.put(PdfName.D, dest);
+ setXrefPartialObject(obj2Idx, ob2);
+ setXrefPartialObject(objIdx, obj);
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Removes all the fields from the document.
+ */
+ public void removeFields() {
+ pageRefs.resetReleasePage();
+ for (int k = 1; k <= pageRefs.size(); ++k) {
+ PdfDictionary page = pageRefs.getPageN(k);
+ PdfArray annots = (PdfArray)getPdfObject(page.get(PdfName.ANNOTS));
+ if (annots == null) {
+ pageRefs.releasePage(k);
+ continue;
+ }
+ ArrayList arr = annots.getArrayList();
+ for (int j = 0; j < arr.size(); ++j) {
+ PdfDictionary annot = (PdfDictionary)getPdfObjectRelease((PdfObject)arr.get(j));
+ if (PdfName.WIDGET.equals(annot.get(PdfName.SUBTYPE)))
+ arr.remove(j--);
+ }
+ if (arr.isEmpty())
+ page.remove(PdfName.ANNOTS);
+ else
+ pageRefs.releasePage(k);
+ }
+ catalog.remove(PdfName.ACROFORM);
+ pageRefs.resetReleasePage();
+ }
+
+ /**
+ * Removes all the annotations and fields from the document.
+ */
+ public void removeAnnotations() {
+ pageRefs.resetReleasePage();
+ for (int k = 1; k <= pageRefs.size(); ++k) {
+ PdfDictionary page = pageRefs.getPageN(k);
+ if (page.get(PdfName.ANNOTS) == null)
+ pageRefs.releasePage(k);
+ else
+ page.remove(PdfName.ANNOTS);
+ }
+ catalog.remove(PdfName.ACROFORM);
+ pageRefs.resetReleasePage();
+ }
+
+ private void iterateBookmarks(PdfObject outlineRef, HashMap names) {
+ while (outlineRef != null) {
+ replaceNamedDestination(outlineRef, names);
+ PdfDictionary outline = (PdfDictionary)getPdfObjectRelease(outlineRef);
+ PdfObject first = outline.get(PdfName.FIRST);
+ if (first != null) {
+ iterateBookmarks(first, names);
+ }
+ outlineRef = outline.get(PdfName.NEXT);
+ }
+ }
+
+ /** Replaces all the local named links with the actual destinations. */
+ public void consolidateNamedDestinations() {
+ if (consolidateNamedDestinations)
+ return;
+ consolidateNamedDestinations = true;
+ HashMap names = getNamedDestination();
+ if (names.size() == 0)
+ return;
+ for (int k = 1; k <= pageRefs.size(); ++k) {
+ PdfDictionary page = pageRefs.getPageN(k);
+ PdfObject annotsRef;
+ PdfArray annots = (PdfArray)getPdfObject(annotsRef = page.get(PdfName.ANNOTS));
+ int annotIdx = lastXrefPartial;
+ releaseLastXrefPartial();
+ if (annots == null) {
+ pageRefs.releasePage(k);
+ continue;
+ }
+ ArrayList list = annots.getArrayList();
+ boolean commitAnnots = false;
+ for (int an = 0; an < list.size(); ++an) {
+ PdfObject objRef = (PdfObject)list.get(an);
+ if (replaceNamedDestination(objRef, names) && !objRef.isIndirect())
+ commitAnnots = true;
+ }
+ if (commitAnnots)
+ setXrefPartialObject(annotIdx, annots);
+ if (!commitAnnots || annotsRef.isIndirect())
+ pageRefs.releasePage(k);
+ }
+ PdfDictionary outlines = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.OUTLINES));
+ if (outlines == null)
+ return;
+ iterateBookmarks(outlines.get(PdfName.FIRST), names);
+ }
+
+ protected static PdfDictionary duplicatePdfDictionary(PdfDictionary original, PdfDictionary copy, PdfReader newReader) {
+ if (copy == null)
+ copy = new PdfDictionary();
+ for (Iterator it = original.getKeys().iterator(); it.hasNext();) {
+ PdfName key = (PdfName)it.next();
+ copy.put(key, duplicatePdfObject(original.get(key), newReader));
+ }
+ return copy;
+ }
+
+ protected static PdfObject duplicatePdfObject(PdfObject original, PdfReader newReader) {
+ if (original == null)
+ return null;
+ switch (original.type()) {
+ case PdfObject.DICTIONARY: {
+ return duplicatePdfDictionary((PdfDictionary)original, null, newReader);
+ }
+ case PdfObject.STREAM: {
+ PRStream org = (PRStream)original;
+ PRStream stream = new PRStream(org, null, newReader);
+ duplicatePdfDictionary(org, stream, newReader);
+ return stream;
+ }
+ case PdfObject.ARRAY: {
+ ArrayList list = ((PdfArray)original).getArrayList();
+ PdfArray arr = new PdfArray();
+ for (Iterator it = list.iterator(); it.hasNext();) {
+ arr.add(duplicatePdfObject((PdfObject)it.next(), newReader));
+ }
+ return arr;
+ }
+ case PdfObject.INDIRECT: {
+ PRIndirectReference org = (PRIndirectReference)original;
+ return new PRIndirectReference(newReader, org.getNumber(), org.getGeneration());
+ }
+ default:
+ return original;
+ }
+ }
+
+ /**
+ * Closes the reader
+ */
+ public void close() {
+ if (!partial)
+ return;
+ try {
+ tokens.close();
+ }
+ catch (IOException e) {
+ throw new ExceptionConverter(e);
+ }
+ }
+
+ protected void removeUnusedNode(PdfObject obj, boolean hits[]) {
+ if (obj == null)
+ return;
+ switch (obj.type()) {
+ case PdfObject.DICTIONARY:
+ case PdfObject.STREAM: {
+ PdfDictionary dic = (PdfDictionary)obj;
+ for (Iterator it = dic.getKeys().iterator(); it.hasNext();) {
+ PdfName key = (PdfName)it.next();
+ PdfObject v = dic.get(key);
+ if (v.isIndirect()) {
+ int num = ((PRIndirectReference)v).getNumber();
+ if (num >= xrefObj.size() || (!partial && xrefObj.get(num) == null)) {
+ dic.put(key, PdfNull.PDFNULL);
+ continue;
+ }
+ }
+ removeUnusedNode(v, hits);
+ }
+ break;
+ }
+ case PdfObject.ARRAY: {
+ ArrayList list = ((PdfArray)obj).getArrayList();
+ for (int k = 0; k < list.size(); ++k) {
+ PdfObject v = (PdfObject)list.get(k);
+ if (v.isIndirect()) {
+ int num = ((PRIndirectReference)v).getNumber();
+ if (num >= xrefObj.size() || (!partial && xrefObj.get(num) == null)) {
+ list.set(k, PdfNull.PDFNULL);
+ continue;
+ }
+ }
+ removeUnusedNode(v, hits);
+ }
+ break;
+ }
+ case PdfObject.INDIRECT: {
+ PRIndirectReference ref = (PRIndirectReference)obj;
+ int num = ref.getNumber();
+ if (!hits[num]) {
+ hits[num] = true;
+ removeUnusedNode(getPdfObjectRelease(ref), hits);
+ }
+ }
+ }
+ }
+
+ /** Removes all the unreachable objects.
+ * @return the number of indirect objects removed
+ */
+ public int removeUnusedObjects() {
+ boolean hits[] = new boolean[xrefObj.size()];
+ removeUnusedNode(trailer, hits);
+ int total = 0;
+ if (partial) {
+ for (int k = 1; k < hits.length; ++k) {
+ if (!hits[k]) {
+ xref[k * 2] = -1;
+ xref[k * 2 + 1] = 0;
+ xrefObj.set(k, null);
+ ++total;
+ }
+ }
+ }
+ else {
+ for (int k = 1; k < hits.length; ++k) {
+ if (!hits[k]) {
+ xrefObj.set(k, null);
+ ++total;
+ }
+ }
+ }
+ return total;
+ }
+
+ /** Gets a read-only version of <CODE>AcroFields</CODE>.
+ * @return a read-only version of <CODE>AcroFields</CODE>
+ */
+ public AcroFields getAcroFields() {
+ return new AcroFields(this, null);
+ }
+
+ /**
+ * Gets the global document JavaScript.
+ * @param file the document file
+ * @throws IOException on error
+ * @return the global document JavaScript
+ */
+ public String getJavaScript(RandomAccessFileOrArray file) throws IOException {
+ PdfDictionary names = (PdfDictionary)getPdfObjectRelease(catalog.get(PdfName.NAMES));
+ if (names == null)
+ return null;
+ PdfDictionary js = (PdfDictionary)getPdfObjectRelease(names.get(PdfName.JAVASCRIPT));
+ if (js == null)
+ return null;
+ HashMap jscript = PdfNameTree.readTree(js);
+ String sortedNames[] = new String[jscript.size()];
+ sortedNames = (String[])jscript.keySet().toArray(sortedNames);
+ Arrays.sort(sortedNames, new StringCompare());
+ StringBuffer buf = new StringBuffer();
+ for (int k = 0; k < sortedNames.length; ++k) {
+ PdfDictionary j = (PdfDictionary)getPdfObjectRelease((PdfIndirectReference)jscript.get(sortedNames[k]));
+ if (j == null)
+ continue;
+ PdfObject obj = getPdfObjectRelease(j.get(PdfName.JS));
+ if (obj.isString())
+ buf.append(((PdfString)obj).toUnicodeString()).append('\n');
+ else if (obj.isStream()) {
+ byte bytes[] = getStreamBytes((PRStream)obj, file);
+ if (bytes.length >= 2 && bytes[0] == (byte)254 && bytes[1] == (byte)255)
+ buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_UNICODE));
+ else
+ buf.append(PdfEncodings.convertToString(bytes, PdfObject.TEXT_PDFDOCENCODING));
+ buf.append('\n');
+ }
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Gets the global document JavaScript.
+ * @throws IOException on error
+ * @return the global document JavaScript
+ */
+ public String getJavaScript() throws IOException {
+ RandomAccessFileOrArray rf = getSafeFile();
+ try {
+ rf.reOpen();
+ return getJavaScript(rf);
+ }
+ finally {
+ try{rf.close();}catch(Exception e){}
+ }
+ }
+
+ /**
+ * Selects the pages to keep in the document. The pages are described as
+ * ranges. The page ordering can be changed but
+ * no page repetitions are allowed. Note that it may be very slow in partial mode.
+ * @param ranges the comma separated ranges as described in {@link SequenceList}
+ */
+ public void selectPages(String ranges) {
+ selectPages(SequenceList.expand(ranges, getNumberOfPages()));
+ }
+
+ /**
+ * Selects the pages to keep in the document. The pages are described as a
+ * <CODE>List</CODE> of <CODE>Integer</CODE>. The page ordering can be changed but
+ * no page repetitions are allowed. Note that it may be very slow in partial mode.
+ * @param pagesToKeep the pages to keep in the document
+ */
+ public void selectPages(List pagesToKeep) {
+ pageRefs.selectPages(pagesToKeep);
+ removeUnusedObjects();
+ }
+
+ /**
+ * @param preferences
+ * @param catalog
+ */
+ public static void setViewerPreferences(int preferences, PdfDictionary catalog) {
+ catalog.remove(PdfName.PAGELAYOUT);
+ catalog.remove(PdfName.PAGEMODE);
+ catalog.remove(PdfName.VIEWERPREFERENCES);
+ if ((preferences & PdfWriter.PageLayoutSinglePage) != 0)
+ catalog.put(PdfName.PAGELAYOUT, PdfName.SINGLEPAGE);
+ else if ((preferences & PdfWriter.PageLayoutOneColumn) != 0)
+ catalog.put(PdfName.PAGELAYOUT, PdfName.ONECOLUMN);
+ else if ((preferences & PdfWriter.PageLayoutTwoColumnLeft) != 0)
+ catalog.put(PdfName.PAGELAYOUT, PdfName.TWOCOLUMNLEFT);
+ else if ((preferences & PdfWriter.PageLayoutTwoColumnRight) != 0)
+ catalog.put(PdfName.PAGELAYOUT, PdfName.TWOCOLUMNRIGHT);
+ else if ((preferences & PdfWriter.PageLayoutTwoPageLeft) != 0)
+ catalog.put(PdfName.PAGELAYOUT, PdfName.TWOPAGELEFT);
+ else if ((preferences & PdfWriter.PageLayoutTwoPageRight) != 0)
+ catalog.put(PdfName.PAGELAYOUT, PdfName.TWOPAGERIGHT);
+ if ((preferences & PdfWriter.PageModeUseNone) != 0)
+ catalog.put(PdfName.PAGEMODE, PdfName.USENONE);
+ else if ((preferences & PdfWriter.PageModeUseOutlines) != 0)
+ catalog.put(PdfName.PAGEMODE, PdfName.USEOUTLINES);
+ else if ((preferences & PdfWriter.PageModeUseThumbs) != 0)
+ catalog.put(PdfName.PAGEMODE, PdfName.USETHUMBS);
+ else if ((preferences & PdfWriter.PageModeFullScreen) != 0)
+ catalog.put(PdfName.PAGEMODE, PdfName.FULLSCREEN);
+ else if ((preferences & PdfWriter.PageModeUseOC) != 0)
+ catalog.put(PdfName.PAGEMODE, PdfName.USEOC);
+ else if ((preferences & PdfWriter.PageModeUseAttachments) != 0)
+ catalog.put(PdfName.PAGEMODE, PdfName.USEATTACHMENTS);
+ if ((preferences & PdfWriter.ViewerPreferencesMask) == 0)
+ return;
+ PdfDictionary vp = new PdfDictionary();
+ if ((preferences & PdfWriter.HideToolbar) != 0)
+ vp.put(PdfName.HIDETOOLBAR, PdfBoolean.PDFTRUE);
+ if ((preferences & PdfWriter.HideMenubar) != 0)
+ vp.put(PdfName.HIDEMENUBAR, PdfBoolean.PDFTRUE);
+ if ((preferences & PdfWriter.HideWindowUI) != 0)
+ vp.put(PdfName.HIDEWINDOWUI, PdfBoolean.PDFTRUE);
+ if ((preferences & PdfWriter.FitWindow) != 0)
+ vp.put(PdfName.FITWINDOW, PdfBoolean.PDFTRUE);
+ if ((preferences & PdfWriter.CenterWindow) != 0)
+ vp.put(PdfName.CENTERWINDOW, PdfBoolean.PDFTRUE);
+ if ((preferences & PdfWriter.DisplayDocTitle) != 0)
+ vp.put(PdfName.DISPLAYDOCTITLE, PdfBoolean.PDFTRUE);
+ if ((preferences & PdfWriter.NonFullScreenPageModeUseNone) != 0)
+ vp.put(PdfName.NONFULLSCREENPAGEMODE, PdfName.USENONE);
+ else if ((preferences & PdfWriter.NonFullScreenPageModeUseOutlines) != 0)
+ vp.put(PdfName.NONFULLSCREENPAGEMODE, PdfName.USEOUTLINES);
+ else if ((preferences & PdfWriter.NonFullScreenPageModeUseThumbs) != 0)
+ vp.put(PdfName.NONFULLSCREENPAGEMODE, PdfName.USETHUMBS);
+ else if ((preferences & PdfWriter.NonFullScreenPageModeUseOC) != 0)
+ vp.put(PdfName.NONFULLSCREENPAGEMODE, PdfName.USEOC);
+ if ((preferences & PdfWriter.DirectionL2R) != 0)
+ vp.put(PdfName.DIRECTION, PdfName.L2R);
+ else if ((preferences & PdfWriter.DirectionR2L) != 0)
+ vp.put(PdfName.DIRECTION, PdfName.R2L);
+ if ((preferences & PdfWriter.PrintScalingNone) != 0)
+ vp.put(PdfName.PRINTSCALING, PdfName.NONE);
+ catalog.put(PdfName.VIEWERPREFERENCES, vp);
+ }
+
+ /**
+ * @param preferences
+ */
+ public void setViewerPreferences(int preferences) {
+ setViewerPreferences(preferences, catalog);
+ }
+
+ /**
+ * @return an int that contains the Viewer Preferences.
+ */
+ public int getViewerPreferences() {
+ int prefs = 0;
+ PdfName name = null;
+ PdfObject obj = getPdfObjectRelease(catalog.get(PdfName.PAGELAYOUT));
+ if (obj != null && obj.isName()) {
+ name = (PdfName)obj;
+ if (name.equals(PdfName.SINGLEPAGE))
+ prefs |= PdfWriter.PageLayoutSinglePage;
+ else if (name.equals(PdfName.ONECOLUMN))
+ prefs |= PdfWriter.PageLayoutOneColumn;
+ else if (name.equals(PdfName.TWOCOLUMNLEFT))
+ prefs |= PdfWriter.PageLayoutTwoColumnLeft;
+ else if (name.equals(PdfName.TWOCOLUMNRIGHT))
+ prefs |= PdfWriter.PageLayoutTwoColumnRight;
+ else if (name.equals(PdfName.TWOPAGELEFT))
+ prefs |= PdfWriter.PageLayoutTwoPageLeft;
+ else if (name.equals(PdfName.TWOPAGERIGHT))
+ prefs |= PdfWriter.PageLayoutTwoPageRight;
+ }
+ obj = getPdfObjectRelease(catalog.get(PdfName.PAGEMODE));
+ if (obj != null && obj.isName()) {
+ name = (PdfName)obj;
+ if (name.equals(PdfName.USENONE))
+ prefs |= PdfWriter.PageModeUseNone;
+ else if (name.equals(PdfName.USEOUTLINES))
+ prefs |= PdfWriter.PageModeUseOutlines;
+ else if (name.equals(PdfName.USETHUMBS))
+ prefs |= PdfWriter.PageModeUseThumbs;
+ else if (name.equals(PdfName.USEOC))
+ prefs |= PdfWriter.PageModeUseOC;
+ else if (name.equals(PdfName.USEATTACHMENTS))
+ prefs |= PdfWriter.PageModeUseAttachments;
+ }
+ obj = getPdfObjectRelease(catalog.get(PdfName.VIEWERPREFERENCES));
+ if (obj == null || !obj.isDictionary())
+ return prefs;
+ PdfDictionary vp = (PdfDictionary)obj;
+ for (int k = 0; k < vpnames.length; ++k) {
+ obj = getPdfObject(vp.get(vpnames[k]));
+ if (obj != null && "true".equals(obj.toString()))
+ prefs |= vpints[k];
+ }
+ obj = getPdfObjectRelease(vp.get(PdfName.PRINTSCALING));
+ if (PdfName.NONE.equals(obj))
+ prefs |= PdfWriter.PrintScalingNone;
+ obj = getPdfObjectRelease(vp.get(PdfName.NONFULLSCREENPAGEMODE));
+ if (obj != null && obj.isName()) {
+ name = (PdfName)obj;
+ if (name.equals(PdfName.USENONE))
+ prefs |= PdfWriter.NonFullScreenPageModeUseNone;
+ else if (name.equals(PdfName.USEOUTLINES))
+ prefs |= PdfWriter.NonFullScreenPageModeUseOutlines;
+ else if (name.equals(PdfName.USETHUMBS))
+ prefs |= PdfWriter.NonFullScreenPageModeUseThumbs;
+ else if (name.equals(PdfName.USEOC))
+ prefs |= PdfWriter.NonFullScreenPageModeUseOC;
+ }
+ obj = getPdfObjectRelease(vp.get(PdfName.DIRECTION));
+ if (obj != null && obj.isName()) {
+ name = (PdfName)obj;
+ if (name.equals(PdfName.L2R))
+ prefs |= PdfWriter.DirectionL2R;
+ else if (name.equals(PdfName.R2L))
+ prefs |= PdfWriter.DirectionR2L;
+ }
+ return prefs;
+ }
+
+ /**
+ * Getter for property appendable.
+ * @return Value of property appendable.
+ */
+ public boolean isAppendable() {
+ return this.appendable;
+ }
+
+ /**
+ * Setter for property appendable.
+ * @param appendable New value of property appendable.
+ */
+ public void setAppendable(boolean appendable) {
+ this.appendable = appendable;
+ if (appendable)
+ getPdfObject(trailer.get(PdfName.ROOT));
+ }
+
+ /**
+ * Getter for property newXrefType.
+ * @return Value of property newXrefType.
+ */
+ public boolean isNewXrefType() {
+ return newXrefType;
+ }
+
+ /**
+ * Getter for property fileLength.
+ * @return Value of property fileLength.
+ */
+ public int getFileLength() {
+ return fileLength;
+ }
+
+ /**
+ * Getter for property hybridXref.
+ * @return Value of property hybridXref.
+ */
+ public boolean isHybridXref() {
+ return hybridXref;
+ }
+
+ static class PageRefs {
+ private PdfReader reader;
+ private IntHashtable refsp;
+ private ArrayList refsn;
+ private ArrayList pageInh;
+ private int lastPageRead = -1;
+ private int sizep;
+
+ private PageRefs(PdfReader reader) throws IOException {
+ this.reader = reader;
+ if (reader.partial) {
+ refsp = new IntHashtable();
+ PdfNumber npages = (PdfNumber)PdfReader.getPdfObjectRelease(reader.rootPages.get(PdfName.COUNT));
+ sizep = npages.intValue();
+ }
+ else {
+ readPages();
+ }
+ }
+
+ PageRefs(PageRefs other, PdfReader reader) {
+ this.reader = reader;
+ this.sizep = other.sizep;
+ if (other.refsn != null) {
+ refsn = new ArrayList(other.refsn);
+ for (int k = 0; k < refsn.size(); ++k) {
+ refsn.set(k, duplicatePdfObject((PdfObject)refsn.get(k), reader));
+ }
+ }
+ else
+ this.refsp = (IntHashtable)other.refsp.clone();
+ }
+
+ int size() {
+ if (refsn != null)
+ return refsn.size();
+ else
+ return sizep;
+ }
+
+ void readPages() throws IOException {
+ if (refsn != null)
+ return;
+ refsp = null;
+ refsn = new ArrayList();
+ pageInh = new ArrayList();
+ iteratePages((PRIndirectReference)reader.catalog.get(PdfName.PAGES));
+ pageInh = null;
+ reader.rootPages.put(PdfName.COUNT, new PdfNumber(refsn.size()));
+ }
+
+ void reReadPages() throws IOException {
+ refsn = null;
+ readPages();
+ }
+
+ /** Gets the dictionary that represents a page.
+ * @param pageNum the page number. 1 is the first
+ * @return the page dictionary
+ */
+ public PdfDictionary getPageN(int pageNum) {
+ PRIndirectReference ref = getPageOrigRef(pageNum);
+ return (PdfDictionary)PdfReader.getPdfObject(ref);
+ }
+
+ /**
+ * @param pageNum
+ * @return a dictionary object
+ */
+ public PdfDictionary getPageNRelease(int pageNum) {
+ PdfDictionary page = getPageN(pageNum);
+ releasePage(pageNum);
+ return page;
+ }
+
+ /**
+ * @param pageNum
+ * @return an indirect reference
+ */
+ public PRIndirectReference getPageOrigRefRelease(int pageNum) {
+ PRIndirectReference ref = getPageOrigRef(pageNum);
+ releasePage(pageNum);
+ return ref;
+ }
+
+ /** Gets the page reference to this page.
+ * @param pageNum the page number. 1 is the first
+ * @return the page reference
+ */
+ public PRIndirectReference getPageOrigRef(int pageNum) {
+ try {
+ --pageNum;
+ if (pageNum < 0 || pageNum >= size())
+ return null;
+ if (refsn != null)
+ return (PRIndirectReference)refsn.get(pageNum);
+ else {
+ int n = refsp.get(pageNum);
+ if (n == 0) {
+ PRIndirectReference ref = getSinglePage(pageNum);
+ if (reader.lastXrefPartial == -1)
+ lastPageRead = -1;
+ else
+ lastPageRead = pageNum;
+ reader.lastXrefPartial = -1;
+ refsp.put(pageNum, ref.getNumber());
+ return ref;
+ }
+ else {
+ if (lastPageRead != pageNum)
+ lastPageRead = -1;
+ return new PRIndirectReference(reader, n);
+ }
+ }
+ }
+ catch (Exception e) {
+ throw new ExceptionConverter(e);
+ }
+ }
+
+ /**
+ * @param pageNum
+ */
+ public void releasePage(int pageNum) {
+ if (refsp == null)
+ return;
+ --pageNum;
+ if (pageNum < 0 || pageNum >= size())
+ return;
+ if (pageNum != lastPageRead)
+ return;
+ lastPageRead = -1;
+ reader.lastXrefPartial = refsp.get(pageNum);
+ reader.releaseLastXrefPartial();
+ refsp.remove(pageNum);
+ }
+
+ /**
+ *
+ */
+ public void resetReleasePage() {
+ if (refsp == null)
+ return;
+ lastPageRead = -1;
+ }
+
+ void insertPage(int pageNum, PRIndirectReference ref) {
+ --pageNum;
+ if (refsn != null) {
+ if (pageNum >= refsn.size())
+ refsn.add(ref);
+ else
+ refsn.add(pageNum, ref);
+ }
+ else {
+ ++sizep;
+ lastPageRead = -1;
+ if (pageNum >= size()) {
+ refsp.put(size(), ref.getNumber());
+ }
+ else {
+ IntHashtable refs2 = new IntHashtable((refsp.size() + 1) * 2);
+ for (Iterator it = refsp.getEntryIterator(); it.hasNext();) {
+ IntHashtable.IntHashtableEntry entry = (IntHashtable.IntHashtableEntry)it.next();
+ int p = entry.getKey();
+ refs2.put(p >= pageNum ? p + 1 : p, entry.getValue());
+ }
+ refs2.put(pageNum, ref.getNumber());
+ refsp = refs2;
+ }
+ }
+ }
+
+ private void pushPageAttributes(PdfDictionary nodePages) {
+ PdfDictionary dic = new PdfDictionary();
+ if (pageInh.size() != 0) {
+ dic.putAll((PdfDictionary)pageInh.get(pageInh.size() - 1));
+ }
+ for (int k = 0; k < pageInhCandidates.length; ++k) {
+ PdfObject obj = nodePages.get(pageInhCandidates[k]);
+ if (obj != null)
+ dic.put(pageInhCandidates[k], obj);
+ }
+ pageInh.add(dic);
+ }
+
+ private void popPageAttributes() {
+ pageInh.remove(pageInh.size() - 1);
+ }
+
+ private void iteratePages(PRIndirectReference rpage) throws IOException {
+ PdfDictionary page = (PdfDictionary)getPdfObject(rpage);
+ PdfArray kidsPR = (PdfArray)getPdfObject(page.get(PdfName.KIDS));
+ if (kidsPR == null) {
+ page.put(PdfName.TYPE, PdfName.PAGE);
+ PdfDictionary dic = (PdfDictionary)pageInh.get(pageInh.size() - 1);
+ PdfName key;
+ for (Iterator i = dic.getKeys().iterator(); i.hasNext();) {
+ key = (PdfName)i.next();
+ if (page.get(key) == null)
+ page.put(key, dic.get(key));
+ }
+ if (page.get(PdfName.MEDIABOX) == null) {
+ PdfArray arr = new PdfArray(new float[]{0,0,PageSize.LETTER.right(),PageSize.LETTER.top()});
+ page.put(PdfName.MEDIABOX, arr);
+ }
+ refsn.add(rpage);
+ }
+ else {
+ page.put(PdfName.TYPE, PdfName.PAGES);
+ pushPageAttributes(page);
+ ArrayList kids = kidsPR.getArrayList();
+ for (int k = 0; k < kids.size(); ++k){
+ PdfObject obj = (PdfObject)kids.get(k);
+ if (!obj.isIndirect()) {
+ while (k < kids.size())
+ kids.remove(k);
+ break;
+ }
+ iteratePages((PRIndirectReference)obj);
+ }
+ popPageAttributes();
+ }
+ }
+
+ protected PRIndirectReference getSinglePage(int n) throws IOException {
+ PdfDictionary acc = new PdfDictionary();
+ PdfDictionary top = reader.rootPages;
+ int base = 0;
+ while (true) {
+ for (int k = 0; k < pageInhCandidates.length; ++k) {
+ PdfObject obj = top.get(pageInhCandidates[k]);
+ if (obj != null)
+ acc.put(pageInhCandidates[k], obj);
+ }
+ PdfArray kids = (PdfArray)PdfReader.getPdfObjectRelease(top.get(PdfName.KIDS));
+ for (Iterator it = kids.listIterator(); it.hasNext();) {
+ PRIndirectReference ref = (PRIndirectReference)it.next();
+ PdfDictionary dic = (PdfDictionary)getPdfObject(ref);
+ int last = reader.lastXrefPartial;
+ PdfObject count = getPdfObjectRelease(dic.get(PdfName.COUNT));
+ reader.lastXrefPartial = last;
+ int acn = 1;
+ if (count != null && count.type() == PdfObject.NUMBER)
+ acn = ((PdfNumber)count).intValue();
+ if (n < base + acn) {
+ if (count == null) {
+ dic.mergeDifferent(acc);
+ return ref;
+ }
+ reader.releaseLastXrefPartial();
+ top = dic;
+ break;
+ }
+ reader.releaseLastXrefPartial();
+ base += acn;
+ }
+ }
+ }
+
+ private void selectPages(List pagesToKeep) {
+ IntHashtable pg = new IntHashtable();
+ ArrayList finalPages = new ArrayList();
+ int psize = size();
+ for (Iterator it = pagesToKeep.iterator(); it.hasNext();) {
+ Integer pi = (Integer)it.next();
+ int p = pi.intValue();
+ if (p >= 1 && p <= psize && pg.put(p, 1) == 0)
+ finalPages.add(pi);
+ }
+ if (reader.partial) {
+ for (int k = 1; k <= psize; ++k) {
+ getPageOrigRef(k);
+ resetReleasePage();
+ }
+ }
+ PRIndirectReference parent = (PRIndirectReference)reader.catalog.get(PdfName.PAGES);
+ PdfDictionary topPages = (PdfDictionary)PdfReader.getPdfObject(parent);
+ ArrayList newPageRefs = new ArrayList(finalPages.size());
+ PdfArray kids = new PdfArray();
+ for (int k = 0; k < finalPages.size(); ++k) {
+ int p = ((Integer)finalPages.get(k)).intValue();
+ PRIndirectReference pref = getPageOrigRef(p);
+ resetReleasePage();
+ kids.add(pref);
+ newPageRefs.add(pref);
+ getPageN(p).put(PdfName.PARENT, parent);
+ }
+ AcroFields af = reader.getAcroFields();
+ boolean removeFields = (af.getFields().size() > 0);
+ for (int k = 1; k <= psize; ++k) {
+ if (!pg.containsKey(k)) {
+ if (removeFields)
+ af.removeFieldsFromPage(k);
+ PRIndirectReference pref = getPageOrigRef(k);
+ int nref = pref.getNumber();
+ reader.xrefObj.set(nref, null);
+ if (reader.partial) {
+ reader.xref[nref * 2] = -1;
+ reader.xref[nref * 2 + 1] = 0;
+ }
+ }
+ }
+ topPages.put(PdfName.COUNT, new PdfNumber(finalPages.size()));
+ topPages.put(PdfName.KIDS, kids);
+ refsp = null;
+ refsn = newPageRefs;
+ }
+ }
+
+ PdfIndirectReference getCryptoRef() {
+ if (cryptoRef == null)
+ return null;
+ return new PdfIndirectReference(0, cryptoRef.getNumber(), cryptoRef.getGeneration());
+ }
+} \ No newline at end of file