/*
* $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 InputStream
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 InputStream
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
* PdfReader.close()
, reopen is automatic.
* @param raf the document location
* @param ownerPassword the password or null
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 PdfReader
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 Rectangle
with the value of the /MediaBox and the /Rotate key.
* @param index the page number. The first page is 1
* @return a Rectangle
*/
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 HashMap
* of String
.
* @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 Rectangle
so that llx and lly are smaller than urx and ury.
* @param box the original rectangle
* @return a normalized Rectangle
*/
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 PdfObject
resolving an indirect reference
* if needed.
* @param obj the PdfObject
to read
* @return the resolved PdfObject
*/
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 PdfObject
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 PdfObject
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 true
to read a correct stream. false
* 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 true
if the document was changed,
* false
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 true
if the PDF is encrypted.
* @return true
if the PDF is encrypted
*/
public boolean isEncrypted() {
return encrypted;
}
/**
* Gets the encryption permissions. It can be used directly in
* PdfWriter.setEncryption()
.
* @return the encryption permissions
*/
public int getPermissions() {
return pValue;
}
/**
* Returns true
if the PDF has a 128 bit key encryption.
* @return true
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 HashMap
. 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 HashMap
. 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 HashMap
. 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 AcroFields
.
* @return a read-only version of AcroFields
*/
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
* List
of Integer
. 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());
}
}