All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jpedal.io.ObjectDecoder Maven / Gradle / Ivy

The newest version!
/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.idrsolutions.com
 * Help section for developers at http://www.idrsolutions.com/java-pdf-library-support/
 *
 * (C) Copyright 1997-2013, IDRsolutions and Contributors.
 *
 * 	This file is part of JPedal
 *
     This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


 *
 * ---------------
 * ObjectDecoder.java
 * ---------------
 */
package org.jpedal.io;

import java.io.Serializable;

import org.jpedal.color.ColorSpaces;
import org.jpedal.constants.PDFflags;
import org.jpedal.exception.PdfSecurityException;
import org.jpedal.objects.raw.ColorSpaceObject;
import org.jpedal.objects.raw.FormObject;
import org.jpedal.objects.raw.NamesObject;
import org.jpedal.objects.raw.ObjectFactory;
import org.jpedal.objects.raw.PdfArrayIterator;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfObject;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.NumberUtils;
import org.jpedal.utils.StringUtils;

public class ObjectDecoder implements Serializable {

	private static final long serialVersionUID = 7141499991569886872L;

	PdfFileReader objectReader = null;

	DecryptionFactory decryption = null;

	// string representation of key only used in debugging
	private Object PDFkey;

	final static byte[] endPattern = { 101, 110, 100, 111, 98, 106 }; // pattern endobj

	static final boolean debugFastCode = false; // objRef.equals("68 0 R")

	private int pdfKeyType, PDFkeyInt;

	/** used in debuggin output */
	static String padding = "";

	boolean isInlineImage = false;

	private int endPt = -1;

	public ObjectDecoder(PdfFileReader pdfFileReader) {
		init(pdfFileReader);
	}

	private ObjectDecoder() {
	}

	private void init(PdfFileReader objectReader) {
		this.objectReader = objectReader;

		this.decryption = objectReader.getDecryptionObject();
	}

	/**
	 * read a dictionary object
	 */
	public int readDictionaryAsObject(PdfObject pdfObject, int i, byte[] raw) {

		if (this.endPt == -1) this.endPt = raw.length;

		// used to debug issues by printing out details for obj
		// (set to non-final above)
		// debugFastCode =pdfObject.getObjectRefAsString().equals("5 0 R");

		if (debugFastCode) padding = padding + "   ";

		final int length = raw.length;

		// show details in debug mode
		if (debugFastCode) ObjectUtils.showData(pdfObject, i, length, raw, padding);

		/**
		 * main loop for read all values from Object data and store in PDF object
		 */
		i = readObjectDataValues(pdfObject, i, raw, length);

		/**
		 * look for stream afterwards
		 */
		if (!pdfObject.ignoreStream() && pdfObject.getGeneralType(-1) != PdfDictionary.ID) readStreamData(pdfObject, i, raw, length);

		/**
		 * we need full names for Forms
		 */
		if (pdfObject.getObjectType() == PdfDictionary.Form) setFieldNames(pdfObject);

		/**
		 * reset indent in debugging
		 */
		if (debugFastCode) {
			int len = padding.length();

			if (len > 3) padding = padding.substring(0, len - 3);
		}

		return i;
	}

	/**
	 * get the values from the data stream and store in PdfObject
	 * 
	 * @param pdfObject
	 * @param i
	 * @param raw
	 * @param length
	 */
	private int readObjectDataValues(PdfObject pdfObject, int i, byte[] raw, int length) {

		int level = 0;
		// allow for no << at start
		if (this.isInlineImage) level = 1;

		while (true) {

			if (i < length && raw[i] == 37) // allow for comment and ignore
			i = stripComment(length, i, raw);

			/**
			 * exit conditions
			 */
			if ((i >= length || (this.endPt != -1 && i >= this.endPt))
					|| (raw[i] == 101 && raw[i + 1] == 110 && raw[i + 2] == 100 && raw[i + 3] == 111)
					|| (raw[i] == 's' && raw[i + 1] == 't' && raw[i + 2] == 'r' && raw[i + 3] == 'e' && raw[i + 4] == 'a' && raw[i + 5] == 'm')) break;

			/**
			 * process value
			 */
			if (raw[i] == 60 && raw[i + 1] == 60) {
				i++;
				level++;
			}
			else
				if (raw[i] == 62 && i + 1 != length && raw[i + 1] == 62 && raw[i - 1] != 62) {
					i++;
					level--;

					if (level == 0) break;
				}
				else
					if (raw[i] == 47 && (raw[i + 1] == 47 || raw[i + 1] == 32)) { // allow for oddity of //DeviceGray and / /DeviceGray in colorspace
						i++;
					}
					else
						if (raw[i] == 47) { // everything from /

							i++; // skip /

							int keyStart = i;
							int keyLength = findDictionaryEnd(i, raw, length);
							i = i + keyLength;

							if (i == length) break;

							// if BDC see if string
							boolean isStringPair = false;
							if (pdfObject.getID() == PdfDictionary.BDC) isStringPair = isStringPair(i, raw, isStringPair);

							int type = pdfObject.getObjectType();

							if (debugFastCode) System.out.println("type=" + type + ' ' + ' ' + pdfObject.getID() + " chars=" + (char) raw[i - 1]
									+ (char) raw[i] + ' ' + pdfObject + " i=" + i + ' ' + isStringPair);

							// see if map of objects
							boolean isMap = isMapObject(pdfObject, i, raw, length, keyStart, keyLength, isStringPair, type);

							if (raw[i] == 47 || raw[i] == 40 || raw[i] == 91) // move back cursor
							i--;

							// check for unknown value and ignore
							if (this.pdfKeyType == -1) i = ObjectUtils.handleUnknownType(i, raw, length);

							/**
							 * now read value
							 */
							if (this.PDFkeyInt == -1 || this.pdfKeyType == -1) {
								if (debugFastCode) System.out.println(padding + pdfObject.getObjectRefAsString()
										+ " =================Not implemented=" + this.PDFkey + " pdfKeyType=" + this.pdfKeyType);
							}
							else {
								i = setValue(pdfObject, i, raw, length, isMap);
							}

							// special case if Dest defined as names object and indirect
						}
						else
							if (raw[i] == '[' && level == 0 && pdfObject.getObjectType() == PdfDictionary.Outlines) {

								ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, raw.length, PdfDictionary.VALUE_IS_MIXED_ARRAY,
										null, PdfDictionary.Names);
								objDecoder.readArray(false, raw, pdfObject, PdfDictionary.Dest);

							}

			i++;

		}

		return i;
	}

	private boolean isMapObject(PdfObject pdfObject, int i, byte[] raw, int length, int keyStart, int keyLength, boolean stringPair, int type) {

		boolean isMap;// ensure all go into 'pool'
		if (type == PdfDictionary.MCID
				&& (pdfObject.getID() == PdfDictionary.RoleMap || (pdfObject.getID() == PdfDictionary.BDC && stringPair) || (pdfObject.getID() == PdfDictionary.A && raw[i - 2] == '/'))) {

			this.pdfKeyType = PdfDictionary.VALUE_IS_NAME;

			// used in debug and this case
			this.PDFkey = PdfDictionary.getKey(keyStart, keyLength, raw);
			this.PDFkeyInt = PdfDictionary.MCID;
			isMap = true;

		}
		else {
			isMap = false;
			this.PDFkey = null;
			getKeyType(pdfObject, i, raw, length, keyLength, keyStart, type);
		}
		return isMap;
	}

	private static int findDictionaryEnd(int jj, byte[] raw, int length) {

		int keyLength = 0;
		while (true) { // get key up to space or [ or / or ( or < or carriage return

			if (raw[jj] == 32 || raw[jj] == 13 || raw[jj] == 9 || raw[jj] == 10 || raw[jj] == 91 || raw[jj] == 47 || raw[jj] == 40 || raw[jj] == 60
					|| raw[jj] == 62) break;

			jj++;
			keyLength++;

			if (jj == length) break;
		}
		return keyLength;
	}

	private void getKeyType(PdfObject pdfObject, int i, byte[] raw, int length, int keyLength, int keyStart, int type) {
		/**
		 * get Dictionary key and type of value it takes
		 */
		if (debugFastCode) // used in debug
		this.PDFkey = PdfDictionary.getKey(keyStart, keyLength, raw);

		this.PDFkeyInt = PdfDictionary.getIntKey(keyStart, keyLength, raw);

		// correct mapping
		if (this.PDFkeyInt == PdfDictionary.Indexed && (type == PdfDictionary.MK || type == PdfDictionary.Form || type == PdfDictionary.Linearized)) this.PDFkeyInt = PdfDictionary.I;

		if (this.isInlineImage) this.PDFkeyInt = PdfObjectFactory.getInlineID(this.PDFkeyInt);

		int id = pdfObject.getID();

		if (type == PdfDictionary.Resources
				&& (this.PDFkeyInt == PdfDictionary.ColorSpace || this.PDFkeyInt == PdfDictionary.ExtGState
						|| this.PDFkeyInt == PdfDictionary.Shading || this.PDFkeyInt == PdfDictionary.XObject || this.PDFkeyInt == PdfDictionary.Font || this.PDFkeyInt == PdfDictionary.Pattern)) {
			this.pdfKeyType = PdfDictionary.VALUE_IS_DICTIONARY_PAIRS;
			// }else if (type==PdfDictionary.Form && id== PdfDictionary.AA && PDFkeyInt== PdfDictionary.K){
			// pdfKeyType= PdfDictionary.VALUE_IS_UNREAD_DICTIONARY;
		}
		else
			if (type == PdfDictionary.Outlines && this.PDFkeyInt == PdfDictionary.D) {
				this.PDFkeyInt = PdfDictionary.Dest;
				this.pdfKeyType = PdfDictionary.VALUE_IS_MIXED_ARRAY;
			}
			else
				if ((type == PdfDictionary.Form || type == PdfDictionary.MK) && this.PDFkeyInt == PdfDictionary.D) {
					if (id == PdfDictionary.AP || id == PdfDictionary.AA) {
						this.pdfKeyType = PdfDictionary.VALUE_IS_VARIOUS;
					}
					else
						if (id == PdfDictionary.Win) {
							this.pdfKeyType = PdfDictionary.VALUE_IS_TEXTSTREAM;
						}
						else {
							this.PDFkeyInt = PdfDictionary.Dest;
							this.pdfKeyType = PdfDictionary.VALUE_IS_MIXED_ARRAY;
						}
				}
				else
					if ((type == PdfDictionary.Form || type == PdfDictionary.MK) && (id == PdfDictionary.AP || id == PdfDictionary.AA)
							&& this.PDFkeyInt == PdfDictionary.A) {
						this.pdfKeyType = PdfDictionary.VALUE_IS_VARIOUS;
					}
					else
						if (this.PDFkeyInt == PdfDictionary.Order && type == PdfDictionary.OCProperties) {
							this.pdfKeyType = PdfDictionary.VALUE_IS_OBJECT_ARRAY;
						}
						else
							if (this.PDFkeyInt == PdfDictionary.Name && type == PdfDictionary.OCProperties) {
								this.pdfKeyType = PdfDictionary.VALUE_IS_TEXTSTREAM;
							}
							else
								if ((type == PdfDictionary.ColorSpace || type == PdfDictionary.Function) && this.PDFkeyInt == PdfDictionary.N) {
									this.pdfKeyType = PdfDictionary.VALUE_IS_FLOAT;
								}
								else
									if (this.PDFkeyInt == PdfDictionary.Gamma && type == PdfDictionary.ColorSpace
											&& pdfObject.getParameterConstant(PdfDictionary.ColorSpace) == ColorSpaces.CalGray) { // its a number not
																																	// an array
										this.pdfKeyType = PdfDictionary.VALUE_IS_FLOAT;
									}
									else
										if (id == PdfDictionary.Win && pdfObject.getObjectType() == PdfDictionary.Form
												&& (this.PDFkeyInt == PdfDictionary.P || this.PDFkeyInt == PdfDictionary.O)) {
											this.pdfKeyType = PdfDictionary.VALUE_IS_TEXTSTREAM;
										}
										else
											if (this.isInlineImage && this.PDFkeyInt == PdfDictionary.ColorSpace) {
												this.pdfKeyType = PdfDictionary.VALUE_IS_DICTIONARY;
											}
											else this.pdfKeyType = PdfDictionary.getKeyType(this.PDFkeyInt, type);

		// handle array of Function in Shading by using keyArray
		if (id == PdfDictionary.Shading && this.PDFkeyInt == PdfDictionary.Function) {

			// get next non number/char value
			int ptr = i;
			while ((raw[ptr] >= 48 && raw[ptr] < 58) || raw[ptr] == 32) {
				ptr++;
			}

			if (raw[ptr] == '[') {
				this.pdfKeyType = PdfDictionary.VALUE_IS_KEY_ARRAY;
			}
		}

		// allow for other values in D,N,R definitions
		/**
		 * if((pdfKeyType==-1 && id== PdfDictionary.ClassMap) || ((pdfKeyType==-1 || (keyLength<3 && (id==PdfDictionary.N || id==PdfDictionary.D ||
		 * id==PdfDictionary.R))) && pdfObject.getParentID()==PdfDictionary.AP && pdfObject.getObjectType()==PdfDictionary.Form &&
		 * (id==PdfDictionary.D || id==PdfDictionary.N || id==PdfDictionary.R)) ){
		 * 
		 * pdfKeyType = getPairedValues(pdfObject, i, raw, pdfKeyType, length, keyLength, keyStart);
		 * 
		 * if(debugFastCode) System.out.println("Returns "+pdfKeyType+" i="+i); }/
		 **/

		/**/
		// allow for other values in D,N,R definitions
		if (this.pdfKeyType == -1 && id == PdfDictionary.ClassMap) {
			this.pdfKeyType = getPairedValues(pdfObject, i, raw, this.pdfKeyType, length, keyLength, keyStart);
		}
		else
			// allow for other values in D,N,R definitions as key pairs
			if (((((id == PdfDictionary.N || id == PdfDictionary.D || id == PdfDictionary.R))) && pdfObject.getParentID() == PdfDictionary.AP
					&& pdfObject.getObjectType() == PdfDictionary.Form && raw[i] != '[')) {

				// get next non number/char value
				int ptr = i;
				while ((raw[ptr] >= 48 && raw[ptr] < 58) || raw[ptr] == 32) {
					ptr++;
				}

				// decide if pair
				if (raw[keyStart] == 'L' && raw[keyStart + 1] == 'e' && raw[keyStart + 2] == 'n' && raw[keyStart + 3] == 'g'
						&& raw[keyStart + 4] == 't' && raw[keyStart + 5] == 'h') {}
				else
					if (raw[keyStart] == 'O' && raw[keyStart + 1] == 'n') {}
					else
						if (raw[keyStart] == 'O' && raw[keyStart + 1] == 'f' && raw[keyStart + 2] == 'f') {}
						else
							if (raw[ptr] == 'R') {
								this.pdfKeyType = getPairedValues(pdfObject, i, raw, this.pdfKeyType, length, keyLength, keyStart);

								if (debugFastCode) System.out.println("new Returns " + this.pdfKeyType + " i=" + i);
							}
			}

		/**/

		// DecodeParms can be an array as well as a dictionary so check next char and alter if so
		if (this.PDFkeyInt == PdfDictionary.DecodeParms) this.pdfKeyType = setTypeForDecodeParams(i, raw, length, this.pdfKeyType);

		if (debugFastCode && this.pdfKeyType == -1 && pdfObject.getObjectType() != PdfDictionary.Page) {
			System.out.println(id + " " + type);
			System.out.println(padding + this.PDFkey + " NO type setting for " + PdfDictionary.getKey(keyStart, keyLength, raw) + " id=" + i);
		}
	}

	private int setValue(PdfObject pdfObject, int i, byte[] raw, int length, boolean map) {

		// if we only need top level do not read whole tree
		boolean ignoreRecursion = pdfObject.ignoreRecursion();

		if (debugFastCode) System.out.println(padding + pdfObject.getObjectRefAsString() + " =================Reading value for key=" + this.PDFkey
				+ " (" + this.PDFkeyInt + ") type=" + PdfDictionary.showAsConstant(this.pdfKeyType) + " ignorRecursion=" + ignoreRecursion + ' '
				+ pdfObject);

		// resolve now in this case as we need to ensure all parts present
		if (this.pdfKeyType == PdfDictionary.VALUE_IS_UNREAD_DICTIONARY && pdfObject.isDataExternal()) this.pdfKeyType = PdfDictionary.VALUE_IS_DICTIONARY;

		switch (this.pdfKeyType) {

		// read text stream (this is text) and also special case of [] in W in CID Fonts
			case PdfDictionary.VALUE_IS_TEXTSTREAM: {
				i = setTextStreamValue(pdfObject, i, raw, ignoreRecursion);
				break;

			}
			case PdfDictionary.VALUE_IS_NAMETREE: {
				i = setNameTreeValue(pdfObject, i, raw, length, ignoreRecursion);
				break;

				// readDictionary keys << /A 12 0 R /B 13 0 R >>
			}
			case PdfDictionary.VALUE_IS_DICTIONARY_PAIRS: {
				i = setDictionaryValue(pdfObject, i, raw, length, ignoreRecursion);
				break;

				// Strings
			}
			case PdfDictionary.VALUE_IS_STRING_ARRAY: {
				ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_STRING_ARRAY);
				i = objDecoder.readArray(ignoreRecursion, raw, pdfObject, this.PDFkeyInt);
				break;

				// read Object Refs in [] (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_BOOLEAN_ARRAY: {
				ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_BOOLEAN_ARRAY);
				i = objDecoder.readArray(false, raw, pdfObject, this.PDFkeyInt);
				break;

				// read Object Refs in [] (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_KEY_ARRAY: {
				ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_KEY_ARRAY);
				i = objDecoder.readArray(ignoreRecursion, raw, pdfObject, this.PDFkeyInt);
				break;

				// read numbers in [] (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_MIXED_ARRAY: {
				ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_MIXED_ARRAY);
				i = objDecoder.readArray(ignoreRecursion, raw, pdfObject, this.PDFkeyInt);
				break;

				// read numbers in [] (may be indirect ref) same as Mixed but allow for recursion and store as objects
			}
			case PdfDictionary.VALUE_IS_OBJECT_ARRAY: {
				ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_OBJECT_ARRAY);
				i = objDecoder.readArray(false, raw, pdfObject, this.PDFkeyInt);
				break;

				// read numbers in [] (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_DOUBLE_ARRAY: {
				ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_DOUBLE_ARRAY);
				i = objDecoder.readArray(false, raw, pdfObject, this.PDFkeyInt);
				break;

				// read numbers in [] (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_INT_ARRAY: {
				ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_INT_ARRAY);
				i = objDecoder.readArray(false, raw, pdfObject, this.PDFkeyInt);
				break;

				// read numbers in [] (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_FLOAT_ARRAY: {
				ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_FLOAT_ARRAY);
				i = objDecoder.readArray(false, raw, pdfObject, this.PDFkeyInt);
				break;

				// read String (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_NAME: {
				i = setNameStringValue(pdfObject, i, raw, map);
				break;

				// read true or false
			}
			case PdfDictionary.VALUE_IS_BOOLEAN: {
				i = setBooleanValue(pdfObject, i, raw, this.PDFkeyInt);
				break;

				// read known set of values
			}
			case PdfDictionary.VALUE_IS_STRING_CONSTANT: {
				i = setStringConstantValue(pdfObject, i, raw);
				break;

				// read known set of values
			}
			case PdfDictionary.VALUE_IS_STRING_KEY: {
				i = setStringKeyValue(pdfObject, i, raw);
				break;

				// read number (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_INT: {

				// roll on
				i++;

				// move cursor to start of text
				while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47)
					i++;

				i = setNumberValue(pdfObject, i, raw, this.PDFkeyInt);
				break;

				// read float number (may be indirect ref)
			}
			case PdfDictionary.VALUE_IS_FLOAT: {
				i = setFloatValue(pdfObject, i, raw, length);
				break;

				// read known Dictionary object which may be direct or indirect
			}
			case PdfDictionary.VALUE_IS_UNREAD_DICTIONARY: {
				i = setUnreadDictionaryValue(pdfObject, i, raw);
				break;

			}
			case PdfDictionary.VALUE_IS_VARIOUS: {
				if (raw.length - 5 > 0 && raw[i + 1] == 'n' && raw[i + 2] == 'u' && raw[i + 3] == 'l' && raw[i + 4] == 'l') { // ignore null value and
																																// skip (ie /N null)
					i = i + 5;
				}
				else {
					i = setVariousValue(pdfObject, i, raw, length, this.PDFkeyInt, map, ignoreRecursion);
				}
				break;

			}
			case PdfDictionary.VALUE_IS_DICTIONARY: {
				i = setDictionaryValue(pdfObject, i, raw, ignoreRecursion);
				break;
			}
		}
		return i;
	}

	private static boolean isStringPair(int i, byte[] raw, boolean stringPair) {

		int len = raw.length;
		for (int aa = i; aa < len; aa++) {
			if (raw[aa] == '(') {
				aa = len;
				stringPair = true;
			}
			else
				if (raw[aa] == '/' || raw[aa] == '>' || raw[aa] == '<' || raw[aa] == '[' || raw[aa] == 'R') {
					aa = len;
				}
				else
					if (raw[aa] == 'M' && raw[aa + 1] == 'C' && raw[aa + 2] == 'I' && raw[aa + 3] == 'D') {
						aa = len;
					}
		}
		return stringPair;
	}

	private static int stripComment(int length, int i, byte[] raw) {

		while (i < length && raw[i] != 10 && raw[i] != 13)
			i++;

		// move cursor to start of text
		while (i < length && (raw[i] == 9 || raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 60))
			i++;

		return i;
	}

	private int setVariousValue(PdfObject pdfObject, int i, byte[] raw, int length, int PDFkeyInt, boolean map, boolean ignoreRecursion) {

		int keyStart;
		if (raw[i] != '<') i++;

		if (debugFastCode) System.out.println(padding + "Various value (first char=" + (char) raw[i] + (char) raw[i + 1] + " )");

		if (raw[i] == '/') {
			i = setNameStringValue(pdfObject, i, raw, map);
		}
		else
			if (raw[i] == 'f' && raw[i + 1] == 'a' && raw[i + 2] == 'l' && raw[i + 3] == 's' && raw[i + 4] == 'e') {
				pdfObject.setBoolean(PDFkeyInt, false);
				i = i + 4;
			}
			else
				if (raw[i] == 't' && raw[i + 1] == 'r' && raw[i + 2] == 'u' && raw[i + 3] == 'e') {
					pdfObject.setBoolean(PDFkeyInt, true);
					i = i + 3;
				}
				else
					if (raw[i] == '(' || (raw[i] == '<' && raw[i - 1] != '<' && raw[i + 1] != '<')) {
						i = readTextStream(pdfObject, i, raw, PDFkeyInt, ignoreRecursion);
					}
					else
						if (raw[i] == '[') {

							if (PDFkeyInt == PdfDictionary.XFA) {
								ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_MIXED_ARRAY);
								i = objDecoder.readArray(ignoreRecursion, raw, pdfObject, PDFkeyInt);
							}
							else
								if (PDFkeyInt == PdfDictionary.K) {
									ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt, PdfDictionary.VALUE_IS_STRING_ARRAY);
									i = objDecoder.readArray(ignoreRecursion, raw, pdfObject, PDFkeyInt);
								}
								else
									if (PDFkeyInt == PdfDictionary.C) {
										ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt,
												PdfDictionary.VALUE_IS_FLOAT_ARRAY);
										i = objDecoder.readArray(ignoreRecursion, raw, pdfObject, PDFkeyInt);
									}
									else {
										ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, i, this.endPt,
												PdfDictionary.VALUE_IS_STRING_ARRAY);
										i = objDecoder.readArray(ignoreRecursion, raw, pdfObject, PDFkeyInt);
									}
						}
						else
							if ((raw[i] == '<' && raw[i + 1] == '<')) {
								i = readDictionary(pdfObject, i, raw, PDFkeyInt, ignoreRecursion);
							}
							else {

								if (debugFastCode) System.out.println(padding + "general case " + i);

								// see if number or ref
								int jj = i;
								int j = i + 1;
								byte[] data = raw;
								int typeFound = 0;
								boolean isNumber = true, isRef = false, isString = false;

								String objRef = pdfObject.getObjectRefAsString();

								while (true) {

									if (data[j] == 'R' && !isString) {

										isRef = true;
										int end = j;
										j = i;
										i = end;

										int ref, generation;

										// allow for [ref] at top level (may be followed by gap
										while (data[j] == 91 || data[j] == 32 || data[j] == 13 || data[j] == 10)
											j++;

										// get object ref
										keyStart = j;
										int refStart = j;
										// move cursor to end of reference
										while (data[j] != 10 && data[j] != 13 && data[j] != 32 && data[j] != 47 && data[j] != 60 && data[j] != 62)
											j++;

										ref = NumberUtils.parseInt(keyStart, j, data);

										// move cursor to start of generation or next value
										while (data[j] == 10 || data[j] == 13 || data[j] == 32)
											// || data[j]==47 || data[j]==60)
											j++;

										/**
										 * get generation number
										 */
										keyStart = j;
										// move cursor to end of reference
										while (data[j] != 10 && data[j] != 13 && data[j] != 32 && data[j] != 47 && data[j] != 60 && data[j] != 62)
											j++;

										generation = NumberUtils.parseInt(keyStart, j, data);

										/**
										 * check R at end of reference and abort if wrong
										 */
										// move cursor to start of R
										while (data[j] == 10 || data[j] == 13 || data[j] == 32 || data[j] == 47 || data[j] == 60)
											j++;

										if (data[j] != 82) // we are expecting R to end ref
										throw new RuntimeException("ref=" + ref + " gen=" + ref + " 1. Unexpected value " + data[j]
												+ " in file - please send to IDRsolutions for analysis char=" + (char) data[j]);

										objRef = new String(data, refStart, 1 + j - refStart);

										// read the Dictionary data
										// boolean setting=debugFastCode;
										byte[] newData = this.objectReader.readObjectAsByteArray(pdfObject,
												this.objectReader.isCompressed(ref, generation), ref, generation);

										// find first valid char to see if String
										int firstChar = 0;
										int newLength = newData.length - 3;
										for (int aa = 3; aa < newLength; aa++) { // skip past 13 0 obj bit at start if present
											if (newData[aa - 2] == 'o' && newData[aa - 1] == 'b' && newData[aa] == 'j') {
												firstChar = aa + 1;
												// roll on past and spaces
												while (firstChar < newLength
														&& (newData[firstChar] == 10 || newData[firstChar] == 13 || newData[firstChar] == 32 || newData[firstChar] == 9))
													firstChar++;

												aa = newLength; // exit loop
											}
											else
												if (newData[aa] > 47 && newData[aa] < 58) {// number
												}
												else
													if (newData[aa] == 'o' || newData[aa] == 'b' || newData[aa] == 'j' || newData[aa] == 'R'
															|| newData[aa] == 32 || newData[aa] == 10 || newData[aa] == 13) { // allowed char
													}
													else { // not expected so reset and quit
														aa = newLength;
														firstChar = 0;
													}
										}

										// stop string with R failing in loop
										isString = newData[firstChar] == '(';

										if (pdfObject.getID() == PdfDictionary.AA && newData[0] == '<' && newData[1] == '<') {

											// create and store stub
											PdfObject valueObj = ObjectFactory.createObject(PDFkeyInt, objRef, pdfObject.getObjectType(),
													pdfObject.getID());
											valueObj.setID(PDFkeyInt);

											pdfObject.setDictionary(PDFkeyInt, valueObj);
											valueObj.setStatus(PdfObject.UNDECODED_DIRECT);
											valueObj.setUnresolvedData(newData, PdfObject.UNDECODED_DIRECT);

											if (debugFastCode) System.out.println(padding + "obj ");

											isNumber = false;
											typeFound = 4;

											i = j;

											break;

										}
										else
											if ((pdfObject.getID() == -1) && newData[0] == '<' && newData[1] == '<') {
												isNumber = false;
												typeFound = 0;

												i = j;

												if (debugFastCode) System.out.println(padding + "Dict starting << ");

												break;

												// allow for indirect on OpenAction
											}
											else
												if (PDFkeyInt == PdfDictionary.OpenAction && data[i] == 'R') {

													// get the object data and pass in
													int jj2 = 0;
													while (newData[jj2] != '[') {
														jj2++;

														if (newData[jj2] == '<' && newData[jj2 + 1] != '<') break;
													}

													ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, jj2, this.endPt,
															PdfDictionary.VALUE_IS_MIXED_ARRAY);
													objDecoder.readArray(ignoreRecursion, newData, pdfObject, PDFkeyInt);
													i = j;
													break;
												}
												else {

													data = newData;

													// allow for data in Linear object not yet loaded
													if (data == null) {
														pdfObject.setFullyResolved(false);

														if (debugFastCode) System.out.println(padding + "Data not yet loaded");

														if (LogWriter.isOutput()) LogWriter.writeLog("[Linearized] " + objRef
																+ " not yet available (4)");

														i = length;
														break;
													}

													jj = 3;

													if (data.length <= 3) {
														jj = 0;
													}
													else {
														while (true) {
															if (data[jj - 2] == 'o' && data[jj - 1] == 'b' && data[jj] == 'j') break;

															jj++;

															if (jj == data.length) {
																jj = 0;
																break;
															}
														}
													}

													if (data[jj] != '(') // do not roll on if text string
													jj++;

													while (data[jj] == 10 || data[jj] == 13 || data[jj] == 32)
														// || data[j]==47 || data[j]==60)
														jj++;

													j = jj;

													if (debugFastCode) System.out.println(j + " >>" + new String(data) + "<' || data[j] == '/') {
													typeFound = 1;
													break;
												}
												else
													if (data[j] == 32 || data[j] == 10 || data[j] == 13) {}
													else
														if ((data[j] >= '0' && data[j] <= '9') || data[j] == '.') { // assume and disprove
														}
														else {
															isNumber = false;
														}
									j++;
									if (j == data.length) break;
								}

								// check if name by counting /
								int count = 0;
								for (int aa = jj + 1; aa < data.length; aa++) {
									if (data[aa] == '/') count++;
								}

								// lose spurious spaces
								while (data[jj] == 10 || data[jj] == 13 || data[jj] == 32)
									// || data[j]==47 || data[j]==60)
									jj++;

								if (typeFound == 4) {// direct ref done above
								}
								else
									if (count == 0 && data[jj] == '/') {

										if (debugFastCode) System.out.println(padding + "NameString ");

										jj = setNameStringValue(pdfObject, jj, data, map);
									}
									else
										if (data[jj] == '(') {

											if (debugFastCode) System.out.println(padding + "Textstream ");

											jj = readTextStream(pdfObject, jj, data, PDFkeyInt, ignoreRecursion);
										}
										else
											if (data[jj] == '[') {

												if (debugFastCode) System.out.println(padding + "Array ");

												ArrayDecoder objDecoder = new ArrayDecoder(this.objectReader, jj, this.endPt,
														PdfDictionary.VALUE_IS_STRING_ARRAY);
												jj = objDecoder.readArray(ignoreRecursion, data, pdfObject, PDFkeyInt);
												/**/
											}
											else
												if (typeFound == 0) {
													if (debugFastCode) System.out.println("Dictionary " + (char) +data[jj] + (char) data[jj + 1]);

													try {
														jj = readDictionaryFromRefOrDirect(-1, pdfObject, objRef, jj, data, PDFkeyInt);

													}
													catch (Exception e) {
														// tell user and log
														if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage());
													}

												}
												else
													if (isNumber) {

														if (debugFastCode) System.out.println("Number");

														jj = setNumberValue(pdfObject, jj, data, PDFkeyInt);

													}
													else
														if (typeFound == 1) {

															if (debugFastCode) System.out.println("Name");

															jj = setNameStringValue(pdfObject, jj, data, map);

														}
														else
															if (debugFastCode) System.out.println(padding + "Not read");

								if (!isRef) i = jj;
							}
		return i;
	}

	private static int setTypeForDecodeParams(int i, byte[] raw, int length, int pdfKeyType) {
		int ii = i;

		// roll onto first valid char
		while (ii < length && (raw[ii] == 32 || raw[ii] == 9 || raw[ii] == 13 || raw[ii] == 10))
			ii++;

		// see if might be object arrays
		if (raw[ii] != '<') {

			// roll onto first valid char
			while (ii < length && (raw[ii] == 32 || raw[ii] == 9 || raw[ii] == 13 || raw[ii] == 10 || raw[ii] == 91))
				ii++;

			if (raw[ii] == '<') pdfKeyType = PdfDictionary.VALUE_IS_OBJECT_ARRAY;

		}
		return pdfKeyType;
	}

	private int setNameTreeValue(PdfObject pdfObject, int i, byte[] raw, int length, boolean ignoreRecursion) {

		boolean isRef = false;

		// move to start
		while (raw[i] != '[') { // can be number as well

			if (raw[i] == '(') { // allow for W (7)
				isRef = false;
				break;
			}

			// allow for number as in refer 9 0 R
			if (raw[i] >= '0' && raw[i] <= '9') {
				isRef = true;
				break;
			}

			i++;
		}

		// allow for direct or indirect
		byte[] data = raw;

		int start = i, j = i;

		int count = 0;

		// read ref data and slot in
		if (isRef) {
			// number
			int keyStart2 = i;
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
				i++;

			int number = NumberUtils.parseInt(keyStart2, i, raw);

			// generation
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			keyStart2 = i;
			// move cursor to end of reference
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
				i++;
			int generation = NumberUtils.parseInt(keyStart2, i, raw);

			// move cursor to start of R
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			if (raw[i] != 82) // we are expecting R to end ref
			throw new RuntimeException("3. Unexpected value in file " + raw[i] + " - please send to IDRsolutions for analysis");

			if (!ignoreRecursion) {

				// read the Dictionary data
				data = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed(number, generation), number, generation);

				// allow for data in Linear object not yet loaded
				if (data == null) {
					pdfObject.setFullyResolved(false);

					if (debugFastCode) System.out.println(padding + "Data not yet loaded");

					if (LogWriter.isOutput()) LogWriter.writeLog("[Linearized] " + pdfObject.getObjectRefAsString() + " not yet available (1)");

					i = length;
					return i;
				}

				// lose obj at start
				j = 3;
				while (data[j - 1] != 106 && data[j - 2] != 98 && data[j - 3] != 111)
					j++;

				// skip any spaces after
				while (data[j] == 10 || data[j] == 13 || data[j] == 32)
					// || data[j]==47 || data[j]==60)
					j++;

				// reset pointer
				start = j;

			}
		}

		// move to end
		while (j < data.length) {

			if (data[j] == '[' || data[j] == '(') count++;
			else
				if (data[j] == ']' || data[j] == ')') count--;

			if (count == 0) break;

			j++;
		}

		if (!ignoreRecursion) {
			int stringLength = j - start + 1;
			byte[] newString = new byte[stringLength];

			System.arraycopy(data, start, newString, 0, stringLength);
			if (pdfObject.getObjectType() != PdfDictionary.Encrypt && this.decryption != null) {
				try {
					newString = this.decryption.decrypt(newString, pdfObject.getObjectRefAsString(), false, null, false, false);
				}
				catch (PdfSecurityException e) {
					// tell user and log
					if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage());
				}
			}

			pdfObject.setTextStreamValue(this.PDFkeyInt, newString);

			if (debugFastCode) System.out.println(padding + "name=" + new String(newString) + " set in " + pdfObject);
		}

		// roll on
		if (!isRef) i = j;
		return i;
	}

	private int setDictionaryValue(PdfObject pdfObject, int i, byte[] raw, boolean ignoreRecursion) {
		/**
		 * workout actual end as not always returned right
		 */
		int end = i;
		int nextC = i;

		// ignore any gaps
		while (raw[nextC] == 10 || raw[nextC] == 32 || raw[nextC] == 9)
			nextC++;

		// allow for null object
		if (raw[nextC] == 'n' && raw[nextC + 1] == 'u' && raw[nextC + 2] == 'l' && raw[nextC + 3] == 'l') {
			i = nextC + 4;
			return i;
		}

		if (raw[i] != '<' && raw[i + 1] != '<') end = end + 2;

		boolean inDictionary = true;
		boolean isKey = raw[end - 1] == '/';

		while (inDictionary) {

			if (raw[end] == '<' && raw[end + 1] == '<') {
				int level2 = 1;
				end++;
				while (level2 > 0) {

					if (raw[end] == '<' && raw[end + 1] == '<') {
						level2++;
						end = end + 2;
					}
					else
						if (raw[end - 1] == '>' && raw[end] == '>') {
							level2--;
							if (level2 > 0) end = end + 2;
						}
						else end++;
				}

				inDictionary = false;

			}
			else
				if (raw[end] == 'R') {
					inDictionary = false;
				}
				else
					if (isKey && (raw[end] == ' ' || raw[end] == 13 || raw[end] == 10 || raw[end] == 9)) {
						inDictionary = false;
					}
					else
						if (raw[end] == '/') {
							inDictionary = false;
							end--;
						}
						else
							if (raw[end] == '>' && raw[end + 1] == '>') {
								inDictionary = false;
								end--;
							}
							else end++;
		}

		// boolean save=debugFastCode;
		readDictionary(pdfObject, i, raw, this.PDFkeyInt, ignoreRecursion);

		// use correct value
		return end;
	}

	private int setFloatValue(PdfObject pdfObject, int i, byte[] raw, int length) {

		// roll on
		i++;

		while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47)
			i++;

		int keyStart = i;

		// move cursor to end of text
		while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62) {
			i++;
		}

		// actual value or first part of ref
		float number = NumberUtils.parseFloat(keyStart, i, raw);

		// roll onto next nonspace char and see if number
		int jj = i;
		while (jj < length && (raw[jj] == 32 || raw[jj] == 13 || raw[jj] == 10))
			jj++;

		// check its not a ref (assumes it XX 0 R)
		if (raw[jj] >= 48 && raw[jj] <= 57) { // if next char is number 0-9 its a ref

			// move cursor to start of generation
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			/**
			 * get generation number
			 */
			keyStart = i;
			// move cursor to end of reference
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
				i++;

			int generation = NumberUtils.parseInt(keyStart, i, raw);

			// move cursor to start of R
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			if (raw[i] != 82) { // we are expecting R to end ref
				throw new RuntimeException("3. Unexpected value in file - please send to IDRsolutions for analysis");
			}

			// read the Dictionary data
			byte[] data = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed((int) number, generation), (int) number,
					generation);

			// allow for data in Linear object not yet loaded
			if (data == null) {
				pdfObject.setFullyResolved(false);

				if (debugFastCode) System.out.println(padding + "Data not yet loaded");

				if (LogWriter.isOutput()) LogWriter.writeLog("[Linearized] " + pdfObject.getObjectRefAsString() + " not yet available (3)");

				i = length;
				return i;
			}

			// lose obj at start
			int j = 3;
			while (data[j - 1] != 106 && data[j - 2] != 98 && data[j - 3] != 111)
				j++;

			// skip any spaces after
			while (data[j] == 10 || data[j] == 13 || data[j] == 32)
				// || data[j]==47 || data[j]==60)
				j++;

			int count = j;

			// skip any spaces at end
			while (data[count] != 10 && data[count] != 13 && data[count] != 32) {// || data[j]==47 || data[j]==60)
				count++;
			}

			number = NumberUtils.parseFloat(j, count, data);

		}

		// store value
		pdfObject.setFloatNumber(this.PDFkeyInt, number);

		if (debugFastCode) System.out.println(padding + "set key in numberValue=" + number);// +" in "+pdfObject);

		i--;// move back so loop works
		return i;
	}

	private int setStringKeyValue(PdfObject pdfObject, int i, byte[] raw) {
		int keyStart;
		int keyLength;
		i++;

		// move cursor to start of text
		while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47)
			i++;

		keyStart = i;
		keyLength = 1;

		boolean isNull = false;

		// move cursor to end of text (allow for null)
		while (raw[i] != 'R' && !isNull) {

			// allow for null for Parent
			if (this.PDFkeyInt == PdfDictionary.Parent && raw[i] == 'n' && raw[i + 1] == 'u' && raw[i + 2] == 'l' && raw[i + 3] == 'l') isNull = true;

			i++;
			keyLength++;
		}

		i--;// move back so loop works

		if (!isNull) {

			// set value
			byte[] stringBytes = new byte[keyLength];
			System.arraycopy(raw, keyStart, stringBytes, 0, keyLength);

			// store value
			pdfObject.setStringKey(this.PDFkeyInt, stringBytes);

			if (debugFastCode) System.out.println(padding + "Set constant in " + pdfObject + " to " + new String(stringBytes));
		}
		return i;
	}

	private int setDictionaryValue(PdfObject pdfObject, int i, byte[] raw, int length, boolean ignoreRecursion) {

		if (debugFastCode) System.out.println(padding + ">>>Reading Dictionary Pairs i=" + i + ' ' + (char) raw[i] + (char) raw[i + 1]
				+ (char) raw[i + 2] + (char) raw[i + 3] + (char) raw[i + 4] + (char) raw[i + 5] + (char) raw[i + 6]);

		// move cursor to start of text
		while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47)
			i++;

		// set data which will be switched below if ref
		byte[] data = raw;
		int j = i;

		// get next key to see if indirect
		boolean isRef = data[j] != '<';

		if (isRef) {

			// number
			int keyStart2 = i;
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
				i++;

			int number = NumberUtils.parseInt(keyStart2, i, raw);

			// generation
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			keyStart2 = i;
			// move cursor to end of reference
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
				i++;
			int generation = NumberUtils.parseInt(keyStart2, i, raw);

			// move cursor to start of R
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			if (raw[i] != 82) // we are expecting R to end ref
			throw new RuntimeException("3. Unexpected value in file " + raw[i] + " - please send to IDRsolutions for analysis");

			if (!ignoreRecursion) {

				// read the Dictionary data
				data = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed(number, generation), number, generation);

				// allow for data in Linear object not yet loaded
				if (data == null) {
					pdfObject.setFullyResolved(false);

					if (debugFastCode) System.out.println(padding + "Data not yet loaded");

					if (LogWriter.isOutput()) LogWriter.writeLog("[Linearized] " + pdfObject.getObjectRefAsString() + " not yet available (2)");

					i = length;
					return i;
				}

				if (data[0] == '<' && data[1] == '<') {
					j = 0;
				}
				else {
					// lose obj at start
					j = 3;

					while (data[j - 1] != 106 && data[j - 2] != 98 && data[j - 3] != 111) {

						if (data[j] == '/') { // trap for odd case
							j = 0;
							break;
						}

						j++;

						if (j == data.length) { // some missing obj so catch these
							j = 0;
							break;
						}
					}

					// skip any spaces after
					while (data[j] == 10 || data[j] == 13 || data[j] == 32)
						// || data[j]==47 || data[j]==60)
						j++;
				}

			}
		}

		// allow for empty object (ie /Pattern <<>> )
		int endJ = j;
		while (data[endJ] == '<' || data[endJ] == ' ' || data[endJ] == 13 || data[endJ] == 10)
			endJ++;

		if (data[endJ] == '>') { // empty object
			j = endJ + 1;
		}
		else {

			PdfObject valueObj = ObjectFactory.createObject(this.PDFkeyInt, pdfObject.getObjectRefAsString(), pdfObject.getObjectType(),
					pdfObject.getID());
			valueObj.setID(this.PDFkeyInt);

			/**
			 * read pairs (stream in data starting at j)
			 */
			if (ignoreRecursion) // just skip to end
			j = readKeyPairs(this.PDFkeyInt, data, j, -2, null);
			else {
				// count values first
				int count = readKeyPairs(this.PDFkeyInt, data, j, -1, null);

				// now set values
				j = readKeyPairs(this.PDFkeyInt, data, j, count, valueObj);

				// store value
				pdfObject.setDictionary(this.PDFkeyInt, valueObj);

				if (debugFastCode) System.out.println(padding + "Set Dictionary " + count + " pairs type in " + pdfObject + " to " + valueObj);
			}
		}

		// update pointer if direct so at end (if ref already in right place)
		if (!isRef) {
			i = j;

			if (debugFastCode) System.out.println(i + ">>>>" + data[i - 2] + ' ' + data[i - 1] + " >" + data[i] + "< " + data[i + 1] + ' '
					+ data[i + 2]);
		}
		return i;
	}

	private int setStringConstantValue(PdfObject pdfObject, int i, byte[] raw) {

		i++;

		// move cursor to start of text
		while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47)
			i++;

		int keyStart = i;
		int keyLength = 0;

		// move cursor to end of text
		while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62) {
			i++;
			keyLength++;
		}

		i--;// move back so loop works

		// store value
		pdfObject.setConstant(this.PDFkeyInt, keyStart, keyLength, raw);

		if (debugFastCode) System.out.println(padding + "Set constant in " + pdfObject + " to "
				+ pdfObject.setConstant(this.PDFkeyInt, keyStart, keyLength, raw));

		return i;
	}

	private int setBooleanValue(PdfObject pdfObject, int i, byte[] raw, int PDFkeyInt) {
		int keyStart;

		i++;

		// move cursor to start of text
		while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47) {
			// System.out.println("skip="+raw[i]);
			i++;
		}

		keyStart = i;

		// move cursor to end of text
		while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62) {
			// System.out.println("key="+raw[i]+" "+(char)raw[i]);
			i++;
		}

		i--;// move back so loop works

		// store value
		if (raw[keyStart] == 't' && raw[keyStart + 1] == 'r' && raw[keyStart + 2] == 'u' && raw[keyStart + 3] == 'e') {
			pdfObject.setBoolean(PDFkeyInt, true);

			if (debugFastCode) System.out.println(padding + "Set Boolean true " + this.PDFkey + " in " + pdfObject);

		}
		else
			if (raw[keyStart] == 'f' && raw[keyStart + 1] == 'a' && raw[keyStart + 2] == 'l' && raw[keyStart + 3] == 's' && raw[keyStart + 4] == 'e') {
				pdfObject.setBoolean(PDFkeyInt, false);

				if (debugFastCode) System.out.println(padding + "Set Boolean false " + this.PDFkey + " in " + pdfObject);

			}
			else throw new RuntimeException("Unexpected value for Boolean value for" + PDFkeyInt + '=' + this.PDFkey);
		return i;
	}

	private int setTextStreamValue(PdfObject pdfObject, int i, byte[] raw, boolean ignoreRecursion) {

		if (raw[i + 1] == 40 && raw[i + 2] == 41) { // allow for empty stream
			i = i + 3;
			pdfObject.setTextStreamValue(this.PDFkeyInt, new byte[1]);

			if (raw[i] == '/') i--;
		}
		else i = readTextStream(pdfObject, i, raw, this.PDFkeyInt, ignoreRecursion);

		return i;
	}

	private void setFieldNames(PdfObject pdfObject) {

		String fieldName = pdfObject.getTextStreamValue(PdfDictionary.T);

		if (fieldName != null) {

			// at this point newString is the raw byte value (99% of the time this is the
			// string but it can be encode in some other ways (like a set of hex values)
			// so we need to use PdfReader.getTextString(newString, false) rather than new String(newString)
			// 6 0 obj <
			//
			// Most of the time you can forget about this because getTextStream() handles it for you
			//
			// Except here where we are manipulating the bytes directly...
			String parent = pdfObject.getStringKey(PdfDictionary.Parent);

			// if no name, or parent has one recursively scan tree for one in Parent
			boolean isMultiple = false;

			while (parent != null) {

				FormObject parentObj = new FormObject(parent, false);
				this.objectReader.readObject(parentObj);

				String newName = parentObj.getTextStreamValue(PdfDictionary.T);
				if (fieldName == null && newName != null) fieldName = newName;
				else
					if (newName != null) {
						// we pass in kids data so stop name.name
						if (!fieldName.equals(newName) || !parent.equals(pdfObject.getObjectRefAsString())) {
							fieldName = newName + '.' + fieldName;
							isMultiple = true;
						}
					}
				if (newName == null) break;

				parent = parentObj.getParentRef();
			}

			// set the field name to be the Fully Qualified Name
			if (isMultiple) pdfObject.setTextStreamValue(PdfDictionary.T, StringUtils.toBytes(fieldName));
		}
	}

	private void readStreamData(PdfObject pdfObject, int i, byte[] raw, int length) {

		for (int xx = i; xx < length - 5; xx++) {

			// avoid reading on subobject ie << /DecodeParams << >> >>
			if (raw[xx] == '>' && raw[xx + 1] == '>') break;

			if (raw[xx] == 's' && raw[xx + 1] == 't' && raw[xx + 2] == 'r' && raw[xx + 3] == 'e' && raw[xx + 4] == 'a' && raw[xx + 5] == 'm') {

				if (debugFastCode) System.out.println(padding + "1. Stream found afterwards");

				if (!pdfObject.isCached()) readStreamIntoObject(pdfObject, xx, raw);

				xx = length;
			}
		}
	}

	private static int getPairedValues(PdfObject pdfObject, int i, byte[] raw, int pdfKeyType, int length, int keyLength, int keyStart) {

		boolean isPair = false;

		int jj = i;

		while (jj < length) {

			// ignore any spaces
			while (jj < length && (raw[jj] == 32 || raw[jj] == 10 || raw[jj] == 13 || raw[jj] == 10))
				jj++;

			// number (possibly reference)
			if (jj < length && raw[jj] >= '0' && raw[jj] <= '9') {

				// rest of ref
				while (jj < length && raw[jj] >= '0' && raw[jj] <= '9')
					jj++;

				// ignore any spaces
				while (jj < length && (raw[jj] == 32 || raw[jj] == 10 || raw[jj] == 13 || raw[jj] == 10))
					jj++;

				// generation and spaces
				while (jj < length && ((raw[jj] >= '0' && raw[jj] <= '9') || (raw[jj] == 32 || raw[jj] == 10 || raw[jj] == 13 || raw[jj] == 10)))
					jj++;

				// not a ref
				if (jj >= length || raw[jj] != 'R') break;

				// roll past R
				jj++;
			}

			// ignore any spaces
			while (jj < length && (raw[jj] == 32 || raw[jj] == 10 || raw[jj] == 13 || raw[jj] == 10))
				jj++;

			// must be next key or end
			if (raw[jj] == '>' && raw[jj + 1] == '>') {
				isPair = true;
				break;
			}
			else
				if (raw[jj] != '/') break;

			jj++;

			// ignore any spaces
			while (jj < length && (raw[jj] != 32 && raw[jj] != 10 && raw[jj] != 13 && raw[jj] != 10))
				jj++;

		}

		if (isPair) {
			pdfObject.setCurrentKey(PdfDictionary.getKey(keyStart, keyLength, raw));
			return PdfDictionary.VALUE_IS_UNREAD_DICTIONARY;
		}
		else return pdfKeyType;
	}

	private int setUnreadDictionaryValue(PdfObject pdfObject, int i, byte[] raw) {

		if (raw[i] != '<') // roll on
		i++;

		while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 9)
			// move cursor to start of text
			i++;

		int start = i, keyStart, keyLength;

		// create and store stub
		PdfObject valueObj = ObjectFactory.createObject(this.PDFkeyInt, pdfObject.getObjectRefAsString(), pdfObject.getObjectType(),
				pdfObject.getID());
		valueObj.setID(this.PDFkeyInt);

		if (raw[i] == 'n' && raw[i + 1] == 'u' && raw[i + 2] == 'l' && raw[i + 3] == 'l') { // allow for null
		}
		else pdfObject.setDictionary(this.PDFkeyInt, valueObj);

		int status = PdfObject.UNDECODED_DIRECT; // assume not object and reset below if wrong

		// some objects can have a common value (ie /ToUnicode /Identity-H
		if (raw[i] == 47) { // not worth caching

			// move cursor to start of text
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			keyStart = i;
			keyLength = 0;

			// move cursor to end of text
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62) {
				i++;
				keyLength++;
			}

			i--;// move back so loop works

			// store value
			int constant = valueObj.setConstant(this.PDFkeyInt, keyStart, keyLength, raw);

			if (constant == PdfDictionary.Unknown || this.isInlineImage) {

				byte[] newStr = new byte[keyLength];
				System.arraycopy(raw, keyStart, newStr, 0, keyLength);

				String s = new String(newStr);
				valueObj.setGeneralStringValue(s);

			}

			status = PdfObject.DECODED;

		}
		else
			// allow for empty object
			if (raw[i] == 'e' && raw[i + 1] == 'n' && raw[i + 2] == 'd' && raw[i + 3] == 'o' && raw[i + 4] == 'b') {}
			else { // we need to ref from ref elsewhere which may be indirect [ref], hence loop

				// roll onto first valid char
				while ((raw[i] == 91 && this.PDFkeyInt != PdfDictionary.ColorSpace) || raw[i] == 32 || raw[i] == 13 || raw[i] == 10) {
					i++;
				}

				// roll on and ignore
				if (raw[i] == '<' && raw[i + 1] == '<') {

					i = i + 2;
					int reflevel = 1;

					while (reflevel > 0) {
						if (raw[i] == '<' && raw[i + 1] == '<') {
							i = i + 2;
							reflevel++;
						}
						else
							if (raw[i] == '>' && i + 1 == raw.length) {
								reflevel = 0;
							}
							else
								if (raw[i] == '>' && raw[i + 1] == '>') {
									i = i + 2;
									reflevel--;
								}
								else i++;
					}
				}
				else
					if (raw[i] == '[') {

						i++;
						int reflevel = 1;

						while (reflevel > 0) {

							if (raw[i] == '(') { // allow for [[ in stream ie [/Indexed /DeviceRGB 255 (abc[[z

								i++;
								while (raw[i] != ')' || ObjectUtils.isEscaped(raw, i))
									i++;

							}
							else
								if (raw[i] == '[') {
									reflevel++;
								}
								else
									if (raw[i] == ']') {
										reflevel--;
									}

							i++;
						}
						i--;
					}
					else
						if (raw[i] == 'n' && raw[i + 1] == 'u' && raw[i + 2] == 'l' && raw[i + 3] == 'l') { // allow for null
							i = i + 4;
						}
						else { // must be a ref

							// assume not object and reset below if wrong
							status = PdfObject.UNDECODED_REF;

							while (raw[i] != 'R' || raw[i - 1] == 'e') { // second condition to stop spurious match on DeviceRGB
								i++;

								if (i == raw.length) break;
							}
							i++;

							if (i >= raw.length) i = raw.length - 1;
						}
			}

		valueObj.setStatus(status);
		if (status != PdfObject.DECODED) {

			int StrLength = i - start;
			byte[] unresolvedData = new byte[StrLength];
			System.arraycopy(raw, start, unresolvedData, 0, StrLength);

			// check for returns in data if ends with R and correct to space
			if (unresolvedData[StrLength - 1] == 82) {

				for (int jj = 0; jj < StrLength; jj++) {

					if (unresolvedData[jj] == 10 || unresolvedData[jj] == 13) unresolvedData[jj] = 32;

				}
			}
			valueObj.setUnresolvedData(unresolvedData, this.PDFkeyInt);

		}

		if (raw[i] == '/' || raw[i] == '>') // move back so loop works
		i--;
		return i;
	}

	int readDictionary(PdfObject pdfObject, int i, byte[] raw, int PDFkeyInt, boolean ignoreRecursion) {

		int keyLength, keyStart;

		String objectRef = pdfObject.getObjectRefAsString();

		// roll on
		if (raw[i] != '<') i++;

		// move cursor to start of text
		while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32)
			i++;

		// some objects can have a common value (ie /ToUnicode /Identity-H
		if (raw[i] == 47) {

			if (debugFastCode) System.out.println(padding + "Indirect");

			// move cursor to start of text
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			keyStart = i;
			keyLength = 0;

			// move cursor to end of text
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62) {
				i++;
				keyLength++;
			}

			i--;// move back so loop works

			if (!ignoreRecursion) {

				PdfObject valueObj = ObjectFactory.createObject(PDFkeyInt, objectRef, pdfObject.getObjectType(), pdfObject.getID());
				valueObj.setID(PDFkeyInt);

				// store value
				int constant = valueObj.setConstant(PDFkeyInt, keyStart, keyLength, raw);

				if (constant == PdfDictionary.Unknown || this.isInlineImage) {

					byte[] newStr = new byte[keyLength];
					System.arraycopy(raw, keyStart, newStr, 0, keyLength);

					String s = new String(newStr);
					valueObj.setGeneralStringValue(s);

					if (debugFastCode) System.out.println(padding + "Set Dictionary as String=" + s + "  in " + pdfObject + " to " + valueObj);

				}
				else
					if (debugFastCode) System.out.println(padding + "Set Dictionary as constant=" + constant + "  in " + pdfObject + " to "
							+ valueObj);

				// store value
				pdfObject.setDictionary(PDFkeyInt, valueObj);

				if (pdfObject.isDataExternal()) {
					valueObj.isDataExternal(true);
					if (!this.resolveFully(valueObj)) pdfObject.setFullyResolved(false);
				}
			}

		}
		else
			// allow for empty object
			if (raw[i] == 'e' && raw[i + 1] == 'n' && raw[i + 2] == 'd' && raw[i + 3] == 'o' && raw[i + 4] == 'b') {
				// return i;

				if (debugFastCode) System.out.println(padding + "Empty object" + new String(raw) + "<<");

			}
			else
				if (raw[i] == '(' && PDFkeyInt == PdfDictionary.JS) { // ie <= '0' && raw[i] <= '9') {
					isRef = true;
					break;
				}

				i++;
			}

			// allow for direct or indirect
			byte[] data = raw;

			int start = i, j = i;

			int count = 0;

			// read ref data and slot in
			if (isRef) {
				// number
				int keyStart2 = i;
				while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62) {

					i++;

				}
				int number = NumberUtils.parseInt(keyStart2, i, raw);

				// generation
				while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
					i++;

				keyStart2 = i;
				// move cursor to end of reference
				while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
					i++;
				int generation = NumberUtils.parseInt(keyStart2, i, raw);

				// move cursor to start of R
				while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
					i++;

				if (raw[i] != 82) // we are expecting R to end ref
				throw new RuntimeException("3. Unexpected value in file " + raw[i] + " - please send to IDRsolutions for analysis");

				if (!ignoreRecursion) {

					// read the Dictionary data
					data = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed(number, generation), number, generation);

					// allow for data in Linear object not yet loaded
					if (data == null) {
						pdfObject.setFullyResolved(false);

						if (debugFastCode) System.out.println(padding + "Data not yet loaded");

						if (LogWriter.isOutput()) LogWriter.writeLog("[Linearized] " + pdfObject.getObjectRefAsString() + " not yet available (6)");

						return raw.length;
					}

					// lose obj at start
					j = 3;
					while (data[j - 1] != 106 && data[j - 2] != 98 && data[j - 3] != 111) {
						j++;

						// catch for error
						if (j == data.length) {
							j = 0;
							break;
						}
					}

					// skip any spaces after
					while (data[j] == 10 || data[j] == 13 || data[j] == 32)
						// || data[j]==47 || data[j]==60)
						j++;

					// reset pointer
					start = j;

				}
			}

			// move to end
			while (j < data.length) {

				if (data[j] == '[' || data[j] == '(') count++;
				else
					if (data[j] == ']' || data[j] == ')') count--;

				if (count == 0) break;

				j++;
			}

			if (!ignoreRecursion) {
				int stringLength = j - start + 1;
				byte[] newString = new byte[stringLength];

				System.arraycopy(data, start, newString, 0, stringLength);

				/**
				 * clean up so matches old string so old code works
				 */
				if (PDFkeyInt != PdfDictionary.JS) { // keep returns in code
					for (int aa = 0; aa < stringLength; aa++) {
						if (newString[aa] == 10 || newString[aa] == 13) newString[aa] = 32;
					}
				}

				pdfObject.setTextStreamValue(PDFkeyInt, newString);

				if (debugFastCode) {
					if (PDFkeyInt == 39) System.out.println(padding + pdfObject + " W=" + new String(newString));
					else System.out.println(padding + pdfObject + " W2=" + new String(newString));
				}
			}

			// roll on
			if (!isRef) i = j;
		}
		else {

			byte[] data;
			try {
				if (raw[i] != '<' && raw[i] != '(') i++;

				while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32)
					i++;

				// allow for no actual value but another key
				if (raw[i] == 47) {
					pdfObject.setTextStreamValue(PDFkeyInt, new byte[1]);
					i--;
					return i;
				}

				if (debugFastCode) {
					System.out.println(padding + "i=" + i + " Reading Text from String=" + new String(raw) + '<');

					System.out.println("-->>");
					for (int zz = i; zz < raw.length; zz++) {
						System.out.print((char) raw[zz]);
					}
					System.out.println("<<--");
				}

				// System.out.println("raw["+i+"]="+(char)raw[i]);
				// get next key to see if indirect
				boolean isRef = raw[i] != '<' && raw[i] != '(';

				int j = i;
				data = raw;
				if (isRef) {

					// number
					int keyStart2 = i;
					while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62) {

						i++;
					}

					int number = NumberUtils.parseInt(keyStart2, i, raw);

					// generation
					while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
						i++;

					keyStart2 = i;
					// move cursor to end of reference
					while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
						i++;
					int generation = NumberUtils.parseInt(keyStart2, i, raw);

					// move cursor to start of R
					while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
						i++;

					if (raw[i] != 82) // we are expecting R to end ref
					return raw.length;
					// throw new RuntimeException(i+" 3. Unexpected value in file " + (char) raw[i - 2]+ (char) raw[i-1] + (char) raw[i] + (char)
					// raw[i+1] + (char)
					// raw[i+2]+(char)raw[i]+" - please send to IDRsolutions for analysis "+pdfObject.getObjectRefAsString()+" "+pdfObject);

					if (!ignoreRecursion) {

						// read the Dictionary data
						data = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed(number, generation), number,
								generation);

						// System.out.println("data read is>>>>>>>>>>>>>>>>>>>\n");
						// for(int ab=0;ab' || data[j] == 0)) break;
					}

				}

				if (!ignoreRecursion) {

					byte[] newString;

					if (data[start] == '<') {
						start++;

						int byteCount = (j - start) >> 1;
						newString = new byte[byteCount];

						int byteReached = 0, topHex, bottomHex;
						while (true) {

							if (start == j) break;

							while (data[start] == 32 || data[start] == 10 || data[start] == 13)
								start++;

							topHex = data[start];

							// convert to number
							if (topHex >= 'A' && topHex <= 'F') {
								topHex = topHex - 55;
							}
							else
								if (topHex >= 'a' && topHex <= 'f') {
									topHex = topHex - 87;
								}
								else
									if (topHex >= '0' && topHex <= '9') {
										topHex = topHex - 48;
									}
									else {

										if (LogWriter.isOutput()) LogWriter.writeLog("Unexpected number " + (char) data[start]);

									}

							start++;

							while (data[start] == 32 || data[start] == 10 || data[start] == 13)
								start++;

							bottomHex = data[start];

							if (bottomHex >= 'A' && bottomHex <= 'F') {
								bottomHex = bottomHex - 55;
							}
							else
								if (bottomHex >= 'a' && bottomHex <= 'f') {
									bottomHex = bottomHex - 87;
								}
								else
									if (bottomHex >= '0' && bottomHex <= '9') {
										bottomHex = bottomHex - 48;
									}
									else {

										if (LogWriter.isOutput()) LogWriter.writeLog("Unexpected number " + (char) data[start]);

										return i;
									}

							start++;

							// calc total
							int finalValue = bottomHex + (topHex << 4);

							newString[byteReached] = (byte) finalValue;

							byteReached++;

						}

					}
					else {
						// roll past (
						if (data[start] == '(') start++;

						newString = ObjectUtils.readEscapedValue(j, data, start, PDFkeyInt == PdfDictionary.ID);
					}

					if (pdfObject.getObjectType() != PdfDictionary.Encrypt) {// && pdfObject.getObjectType()!=PdfDictionary.Outlines){

						try {
							if (this.decryption != null && !pdfObject.isInCompressedStream()) newString = this.decryption.decryptString(newString,
									pdfObject.getObjectRefAsString());
						}
						catch (PdfSecurityException e) {
							// tell user and log
							if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage());
						}
					}

					pdfObject.setTextStreamValue(PDFkeyInt, newString);

					if (debugFastCode) System.out.println(padding + "TextStream=" + new String(newString) + " in pdfObject=" + pdfObject);
				}

				if (!isRef) i = j;

			}
			catch (Exception e) {
				// tell user and log
				if (LogWriter.isOutput()) LogWriter.writeLog("Exception: " + e.getMessage());
			}
		}
		return i;
	}

	int setNumberValue(PdfObject pdfObject, int i, byte[] raw, int PDFkeyInt) {

		int keyStart = i, rawLength = raw.length;

		// move cursor to end of text
		while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62 && raw[i] != '(' && raw[i] != '.') {
			i++;
		}

		// actual value or first part of ref
		int number = NumberUtils.parseInt(keyStart, i, raw);

		// roll onto next nonspace char and see if number
		int jj = i;
		while (jj < rawLength && (raw[jj] == 32 || raw[jj] == 13 || raw[jj] == 10))
			jj++;

		boolean isRef = false;

		// check its not a ref (assumes it XX 0 R)
		if (raw[jj] >= 48 && raw[jj] <= 57) { // if next char is number 0-9 it may be a ref

			int aa = jj;

			// move cursor to end of number
			while ((raw[aa] != 10 && raw[aa] != 13 && raw[aa] != 32 && raw[aa] != 47 && raw[aa] != 60 && raw[aa] != 62))
				aa++;

			// move cursor to start of text
			while (aa < rawLength && (raw[aa] == 10 || raw[aa] == 13 || raw[aa] == 32 || raw[aa] == 47))
				aa++;

			isRef = aa < rawLength && raw[aa] == 'R';

		}

		if (isRef) {
			// move cursor to start of generation
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			/**
			 * get generation number
			 */
			keyStart = i;
			// move cursor to end of reference
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
				i++;

			int generation = NumberUtils.parseInt(keyStart, i, raw);

			// move cursor to start of R
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			if (raw[i] != 82) { // we are expecting R to end ref
				throw new RuntimeException("3. Unexpected value in file - please send to IDRsolutions for analysis");
			}

			// read the Dictionary data
			byte[] data = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed(number, generation), number, generation);

			// allow for data in Linear object not yet loaded
			if (data == null) {
				pdfObject.setFullyResolved(false);

				if (debugFastCode) System.out.println(padding + "Data not yet loaded");

				if (LogWriter.isOutput()) LogWriter.writeLog("[Linearized] " + pdfObject.getObjectRefAsString() + " not yet available (8)");

				return rawLength;
			}

			// lose obj at start
			int j = 0, len = data.length;

			// allow for example where start <<
			if (len > 1 && data[0] == '<' && data[1] == '<') {}
			else
				if (len < 3) { // fix for short indirect value /K 30 0 R where 30 0R is -1 ie (Customers-Dec2011/Real Estate Tax Bill 2011.pdf)
				}
				else {
					j = 3;
					if (len > 3) { // allow for small values (ie Rotate object pointing to value 0)
						while (data[j - 1] != 106 && data[j - 2] != 98 && data[j - 3] != 111) {
							j++;

							if (j == len) {
								j = 0;
								break;
							}
						}
					}
				}

			// skip any spaces after
			if (len > 1) {// allow for small values (ie Rotate object pointing to value 0)
				while (j < data.length && (data[j] == 9 || data[j] == 10 || data[j] == 13 || data[j] == 32))
					// || data[j]==47 || data[j]==60)
					j++;
			}

			int count = j;

			// skip any spaces at end
			while (count < len && data[count] != 9 && data[count] != 10 && data[count] != 13 && data[count] != 32)
				// || data[j]==47 || data[j]==60)
				count++;

			number = NumberUtils.parseInt(j, count, data);

		}

		// store value
		pdfObject.setIntNumber(PDFkeyInt, number);

		if (debugFastCode) System.out.println(padding + "set key=" + this.PDFkey + " numberValue=" + number);// +" in "+pdfObject);

		i--;// move back so loop works

		return i;
	}

	/**
	 * @param id
	 * @param pdfObject
	 * @param objectRef
	 * @param i
	 * @param raw
	 * @param PDFkeyInt
	 *            - -1 will store in pdfObject directly, not as separate object
	 */
	int readDictionaryFromRefOrDirect(int id, PdfObject pdfObject, String objectRef, int i, byte[] raw, int PDFkeyInt) {

		readDictionaryFromRefOrDirect: while (true) {

			int keyStart;
			int possibleArrayStart = -1;

			// @speed - find end so we can ignore once no longer reading into map as well
			// and skip to end of object
			// allow for [ref] or [<< >>] at top level (may be followed by gap)
			// good example is /PDFdata/baseline_screens/docusign/test3 _ Residential Purchase and Sale Agreement - 6-03.pdf
			while (raw[i] == 91 || raw[i] == 32 || raw[i] == 13 || raw[i] == 10) {

				if (raw[i] == 91) // track incase /Mask [19 19]
				possibleArrayStart = i;

				i++;
			}

			// some items like MAsk can be [19 19] or stream
			// and colorspace is law unto itself
			if (PDFkeyInt == PdfDictionary.ColorSpace || id == PdfDictionary.ColorSpace || pdfObject.getPDFkeyInt() == PdfDictionary.ColorSpace) {
				ColorObjectDecoder colDecoder = new ColorObjectDecoder(this.objectReader);
				return colDecoder.processColorSpace(pdfObject, pdfObject.getObjectRefAsString(), i, raw);
			}
			else
				if (possibleArrayStart != -1
						&& (PDFkeyInt == PdfDictionary.Mask || PDFkeyInt == PdfDictionary.TR || PDFkeyInt == PdfDictionary.OpenAction)) return processArray(
						pdfObject, raw, PDFkeyInt, possibleArrayStart);

			if (raw[i] == '%') { // if %comment roll onto next line
				while (raw[i] != 13 && raw[i] != 10)
					i++;

				// and lose space after
				while (raw[i] == 91 || raw[i] == 32 || raw[i] == 13 || raw[i] == 10)
					i++;
			}

			if (raw[i] == 60) { // [<>]

				i = convertDirectDictionaryToObject(pdfObject, objectRef, i, raw, PDFkeyInt);

			}
			else
				if (raw[i] == 47) { // direct value such as /DeviceGray

					i = ObjectUtils.setDirectValue(pdfObject, i, raw, PDFkeyInt);

				}
				else { // ref or [ref]

					int j = i, ref, generation;
					byte[] data = raw;

					while (true) {

						// allow for [ref] at top level (may be followed by gap
						while (data[j] == 91 || data[j] == 32 || data[j] == 13 || data[j] == 10)
							j++;

						// trap nulls as well
						boolean hasNull = false;

						while (true) {

							// trap null arrays ie [null null]
							if (hasNull && data[j] == ']') return j;

							/**
							 * get object ref
							 */
							keyStart = j;
							// move cursor to end of reference
							while (data[j] != 10 && data[j] != 13 && data[j] != 32 && data[j] != 47 && data[j] != 60 && data[j] != 62) {

								// trap null arrays ie [null null] or [null]

								if (data[j] == 'l' && data[j - 1] == 'l' && data[j - 2] == 'u' && data[j - 3] == 'n') hasNull = true;

								if (hasNull && data[j] == ']') return j;

								j++;
							}

							ref = NumberUtils.parseInt(keyStart, j, data);

							// move cursor to start of generation or next value
							while (data[j] == 10 || data[j] == 13 || data[j] == 32)
								// || data[j]==47 || data[j]==60)
								j++;

							// handle nulls
							if (ref != 69560 || data[keyStart] != 'n') break; // not null
							else {
								hasNull = true;
								if (data[j] == '<') { // /DecodeParms [ null << /K -1 /Columns 1778 >> ] ignore null and jump down to enclosed
														// Dictionary
									i = j;
									continue readDictionaryFromRefOrDirect;

								}
							}
						}

						/**
						 * get generation number
						 */
						keyStart = j;
						// move cursor to end of reference
						while (data[j] != 10 && data[j] != 13 && data[j] != 32 && data[j] != 47 && data[j] != 60 && data[j] != 62)
							j++;

						generation = NumberUtils.parseInt(keyStart, j, data);

						/**
						 * check R at end of reference and abort if wrong
						 */
						// move cursor to start of R
						while (data[j] == 10 || data[j] == 13 || data[j] == 32 || data[j] == 47 || data[j] == 60)
							j++;

						if (data[j] != 82) // we are expecting R to end ref
						throw new RuntimeException("ref=" + ref + " gen=" + ref + " 1. Unexpected value " + data[j]
								+ " in file - please send to IDRsolutions for analysis char=" + (char) data[j]);

						// read the Dictionary data
						data = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed(ref, generation), ref, generation);

						// allow for data in Linear object not yet loaded
						if (data == null) {
							pdfObject.setFullyResolved(false);

							if (LogWriter.isOutput()) LogWriter.writeLog("[Linearized] " + pdfObject.getObjectRefAsString()
									+ " not yet available (11)");

							return raw.length;
						}

						// disregard corrputed data from start of file
						if (data != null && data.length > 4 && data[0] == '%' && data[1] == 'P' && data[2] == 'D' && data[3] == 'F') data = null;

						if (data == null) break;

						/**
						 * get not indirect and exit if not
						 */
						int j2 = 0;

						// allow for [91 0 r]
						if (data[j2] != '[' && data[0] != '<' && data[1] != '<') {

							while (j2 < 3 || (j2 > 2 && data[j2 - 1] != 106 && data[j2 - 2] != 98 && data[j2 - 3] != 111)) {

								// allow for /None as value
								if (data[j2] == '/') break;
								j2++;
							}

							// skip any spaces
							while (data[j2] != 91 && (data[j2] == 10 || data[j2] == 13 || data[j2] == 32))
								// || data[j]==47 || data[j]==60)
								j2++;
						}

						// if indirect, round we go again
						if (data[j2] != 91) {
							j = 0;
							break;
						}

						j = j2;
					}

					// allow for no data found (ie /PDFdata/baseline_screens/debug/hp_broken_file.pdf)
					if (data != null) {

						/**
						 * get id from stream
						 */
						// skip any spaces
						while (data[j] == 10 || data[j] == 13 || data[j] == 32)
							// || data[j]==47 || data[j]==60)
							j++;

						boolean isMissingValue = j < raw.length && raw[j] == '<';

						if (isMissingValue) { // check not <>)
			int objCount = 0, refStarts = -1, refEnds = -1;
			if (raw[0] == '<') {
				for (int ii = 0; ii < raw.length; ii++) {

					// count keys
					if (raw[ii] == '/') objCount++;
					// find start of ref
					if (objCount == 1) {
						if (refStarts == -1) {
							if (raw[ii] > '0' && raw[ii] < '9') refStarts = ii;
						}
						else {
							if (raw[ii] == 'R') refEnds = ii + 1;
						}
					}
				}

				if (objCount == 1 && refStarts != -1 && refEnds != -1) {
					objectRef = new String(raw, refStarts, refEnds - refStarts);
					valueObj.setRef(objectRef);
				}
			}

		}
		else {
			valueObj = ObjectFactory.createObject(PDFkeyInt, objectRef, pdfObject.getObjectType(), pdfObject.getID());
			// 
			valueObj.setInCompressedStream(pdfObject.isInCompressedStream());
			// 
			valueObj.setID(PDFkeyInt);

			// if it is cached, we need to copy across data so in correct Obj
			// (we read data before we created object so in wrong obj at this point)
			if (pdfObject.isCached()) valueObj.moveCacheValues(pdfObject);

			if (debugFastCode) System.out.println("valueObj=" + valueObj + " pdfObject=" + pdfObject + " PDFkeyInt=" + PDFkeyInt + ' '
					+ pdfObject.getID() + ' ' + pdfObject.getParentID());
		}

		if (debugFastCode) System.out.println(padding + "Reading [<>] to " + valueObj + " into " + pdfObject + " i=" + i);

		ObjectDecoder objDecoder = new ObjectDecoder(this.objectReader);
		i = objDecoder.readDictionaryAsObject(valueObj, i, raw);

		// needed to ensure >>>> works
		if (i < raw.length && raw[i] == '>') i--;

		if (debugFastCode) {
			System.out.println(padding + "data " + valueObj + " into pdfObject=" + pdfObject + " i=" + i);
		}

		// store value (already set above for -1
		if (PDFkeyInt != -1) pdfObject.setDictionary(PDFkeyInt, valueObj);

		// roll on to end
		int count = raw.length;
		while (i < count - 1 && raw[i] == 62 && raw[i + 1] == 62) { //
			i++;
			if (i + 1 < raw.length && raw[i + 1] == 62) // allow for >>>>
			break;
		}
		return i;
	}

	/**
	 * if pairs is -1 returns number of pairs otherwise sets pairs and returns point reached in stream
	 */
	private int readKeyPairs(int id, byte[] data, int j, int pairs, PdfObject pdfObject) {

		final boolean debug = false;

		int start = j, level;

		int numberOfPairs = pairs;

		if (debug) {
			int length = data.length;
			System.out.println("count=" + pairs + "============================================\n");
			for (int aa = j; aa < length; aa++) {
				System.out.print((char) data[aa]);

				if (aa > 5 && data[aa - 5] == 's' && data[aa - 4] == 't' && data[aa - 3] == 'r' && data[aa - 2] == 'e' && data[aa - 1] == 'a'
						&& data[aa] == 'm') aa = length;
			}
			System.out.println("\n============================================");
		}

		// same routine used to count first and then fill with values
		boolean isCountOnly = false, skipToEnd = false;
		byte[][] keys = null, values = null;
		PdfObject[] objs = null;

		if (pairs == -1) {
			isCountOnly = true;
		}
		else
			if (pairs == -2) {
				isCountOnly = true;
				skipToEnd = true;
			}
			else {
				keys = new byte[numberOfPairs][];
				values = new byte[numberOfPairs][];
				objs = new PdfObject[numberOfPairs];

				if (debug) System.out.println("Loading " + numberOfPairs + " pairs");
			}
		pairs = 0;

		while (true) {

			// move cursor to start of text
			while (data[start] == 9 || data[start] == 10 || data[start] == 13 || data[start] == 32 || data[start] == 60)
				start++;

			// allow for comment
			if (data[start] == 37) {
				while (data[start] != 10 && data[start] != 13) {
					// System.out.println(data[start]+" "+(char)data[start]);
					start++;
				}

				// move cursor to start of text
				while (data[start] == 9 || data[start] == 10 || data[start] == 13 || data[start] == 32 || data[start] == 60)
					start++;
			}

			// exit at end
			if (data[start] == 62) break;

			// count token or tell user problem
			if (data[start] == 47) {
				pairs++;
				start++;
			}
			else throw new RuntimeException("Unexpected value " + data[start] + " - not key pair");

			// read token key and save if on second run
			int tokenStart = start;
			while (data[start] != 32 && data[start] != 10 && data[start] != 13 && data[start] != '[' && data[start] != '<' && data[start] != '/')
				start++;

			int tokenLength = start - tokenStart;

			byte[] tokenKey = new byte[tokenLength];
			System.arraycopy(data, tokenStart, tokenKey, 0, tokenLength);

			if (!isCountOnly) // pairs already rolled on so needs to be 1 less
			keys[pairs - 1] = tokenKey;

			// now skip any spaces to key or text
			while (data[start] == 10 || data[start] == 13 || data[start] == 32)
				start++;

			boolean isDirect = data[start] == 60 || data[start] == '[' || data[start] == '/';

			byte[] dictData;

			if (debug) System.out.println("token=" + new String(tokenKey) + " isDirect " + isDirect);

			if (isDirect) {
				// get to start at <<
				while (data[start - 1] != '<' && data[start] != '<' && data[start] != '[' && data[start] != '/')
					start++;

				int streamStart = start;

				// find end
				boolean isObject = true;

				if (data[start] == '<') {
					start = start + 2;
					level = 1;

					while (level > 0) {
						// System.out.print((char)data[start]);
						if (data[start] == '<' && data[start + 1] == '<') {
							start = start + 2;
							level++;
						}
						else
							if (data[start] == '>' && data[start + 1] == '>') {
								start = start + 2;
								level--;
							}
							else start++;
					}

					// System.out.println("\n<---------------"+start);

					// if(data[start]=='>' && data[start+1]=='>')
					// start=start+2;
				}
				else
					if (data[start] == '[') {

						level = 1;
						start++;

						boolean inStream = false;

						while (level > 0) {

							// allow for streams
							if (!inStream && data[start] == '(') inStream = true;
							else
								if (inStream && data[start] == ')' && (data[start - 1] != '\\' || data[start - 2] == '\\')) inStream = false;

							// System.out.println((char)data[start]);

							if (!inStream) {
								if (data[start] == '[') level++;
								else
									if (data[start] == ']') level--;
							}

							start++;
						}

						isObject = false;
					}
					else
						if (data[start] == '/') {
							start++;
							while (data[start] != '/' && data[start] != 10 && data[start] != 13 && data[start] != 32) {
								start++;

								if (start < data.length - 1 && data[start] == '>' && data[start + 1] == '>') break;
							}
						}

				if (!isCountOnly) {
					int len = start - streamStart;
					dictData = new byte[len];
					System.arraycopy(data, streamStart, dictData, 0, len);
					// pairs already rolled on so needs to be 1 less
					values[pairs - 1] = dictData;

					String ref = pdfObject.getObjectRefAsString();

					// @speed - will probably need to change as we add more items

					if (pdfObject.getObjectType() == PdfDictionary.ColorSpace) {

						ColorObjectDecoder colDecoder = new ColorObjectDecoder(this.objectReader);

						// isDirect avoids storing multiple direct objects as will overwrite each other
						if (isObject && !isDirect) {
							colDecoder.handleColorSpaces(pdfObject, 0, dictData);
							objs[pairs - 1] = pdfObject;
						}
						else {
							ColorSpaceObject colObject = new ColorSpaceObject(ref);

							if (isDirect) colObject.setRef(-1, 0);

							colDecoder.handleColorSpaces(colObject, 0, dictData);
							objs[pairs - 1] = colObject;
						}

						// handleColorSpaces(-1, valueObj,ref, 0, dictData,debug, -1,null, paddingString);
					}
					else
						if (isObject) {

							PdfObject valueObj = ObjectFactory.createObject(id, ref, pdfObject.getObjectType(), pdfObject.getID());
							valueObj.setID(id);
							readDictionaryFromRefOrDirect(id, valueObj, ref, 0, dictData, -1);
							objs[pairs - 1] = valueObj;
						}

					// lose >> at end
					// while(start 5 && rawDictData[aa - 5] == 's' && rawDictData[aa - 4] == 't' && rawDictData[aa - 3] == 'r'
												&& rawDictData[aa - 2] == 'e' && rawDictData[aa - 1] == 'a' && rawDictData[aa] == 'm') aa = rawDictData.length;
									}
									System.out.println("\n============================================");
								}
								// cleanup
								// lose obj at start
								int jj = 0;

								while (jj < 3 || (rawDictData[jj - 1] != 106 && rawDictData[jj - 2] != 98 && rawDictData[jj - 3] != 111)) {

									if (rawDictData[jj] == '/' || rawDictData[jj] == '[' || rawDictData[jj] == '<') break;

									jj++;

									if (jj == rawDictData.length) {
										jj = 0;
										break;
									}
								}

								// skip any spaces after
								while (rawDictData[jj] == 10 || rawDictData[jj] == 13 || rawDictData[jj] == 32)
									// || data[j]==47 || data[j]==60)
									jj++;

								int len = rawDictData.length - jj;
								dictData = new byte[len];
								System.arraycopy(rawDictData, jj, dictData, 0, len);
								// pairs already rolled on so needs to be 1 less
								values[pairs - 1] = dictData;

								String ref = number + " " + generation + " R";// pdfObject.getObjectRefAsString();

								if (pdfObject.getObjectType() == PdfDictionary.Font && id == PdfDictionary.Font) {// last condition for CharProcs
									objs[pairs - 1] = null;
									values[pairs - 1] = StringUtils.toBytes(ref);
								}
								else
									if (pdfObject.getObjectType() == PdfDictionary.XObject) {
										// intel Unimplemented pattern type 0 in file
										PdfObject valueObj = ObjectFactory.createObject(id, ref, PdfDictionary.XObject, PdfDictionary.XObject);
										valueObj.setStatus(PdfObject.UNDECODED_REF);
										valueObj.setUnresolvedData(StringUtils.toBytes(ref), id);

										objs[pairs - 1] = valueObj;
									}
									else {

										// @speed - will probably need to change as we add more items
										PdfObject valueObj = ObjectFactory.createObject(id, ref, pdfObject.getObjectType(), pdfObject.getID());
										valueObj.setID(id);
										if (debug) {
											System.out.println(ref + " ABOUT TO READ OBJ for " + valueObj + ' ' + pdfObject);

											System.out.println("-------------------\n");
											for (int aa = 0; aa < dictData.length; aa++) {
												System.out.print((char) dictData[aa]);

												if (aa > 5 && dictData[aa - 5] == 's' && dictData[aa - 4] == 't' && dictData[aa - 3] == 'r'
														&& dictData[aa - 2] == 'e' && dictData[aa - 1] == 'a' && dictData[aa] == 'm') aa = dictData.length;
											}
											System.out.println("\n-------------------");
										}

										if (valueObj.getObjectType() == PdfDictionary.ColorSpace) {
											ColorObjectDecoder colDecoder = new ColorObjectDecoder(this.objectReader);
											colDecoder.handleColorSpaces(valueObj, 0, dictData);
										}
										else readDictionaryFromRefOrDirect(id, valueObj, ref, 0, dictData, -1);

										objs[pairs - 1] = valueObj;

									}
							}
					}
				}
			}
		}

		if (!isCountOnly) pdfObject.setDictionaryPairs(keys, values, objs);

		if (debug) System.out.println("done=============================================");

		if (skipToEnd || !isCountOnly) return start;
		else return pairs;
	}

	void readStreamIntoObject(PdfObject pdfObject, int j, byte[] data) {

		int count = data.length;

		if (debugFastCode) System.out.println(padding + "Looking for stream");

		byte[] stream = null;

		/**
		 * see if JBIG encoded
		 */
		PdfArrayIterator maskFilters = pdfObject.getMixedArray(PdfDictionary.Filter);

		// get type as need different handling
		boolean isJBigEncoded = false;
		int firstMaskValue = PdfDictionary.Unknown;
		if (maskFilters != null && maskFilters.hasMoreTokens()) {

			firstMaskValue = maskFilters.getNextValueAsConstant(true);

			if (firstMaskValue == PdfFilteredReader.JBIG2Decode) isJBigEncoded = true;

			while (maskFilters.hasMoreTokens() && !isJBigEncoded) {
				firstMaskValue = maskFilters.getNextValueAsConstant(true);
				if (firstMaskValue == PdfFilteredReader.JBIG2Decode) isJBigEncoded = true;
			}
		}

		for (int a = j; a < count; a++) {
			if ((data[a] == 115) && (data[a + 1] == 116) && (data[a + 2] == 114) && (data[a + 3] == 101) && (data[a + 4] == 97)
					&& (data[a + 5] == 109)) {

				// ignore these characters and first return
				a = a + 6;

				while (data[a] == 32)
					a++;

				if (data[a] == 13 && data[a + 1] == 10) // allow for double linefeed
				a = a + 2;
				// see /PDFdata/baseline_screens/customers-june2011/Agency discl. Wabash.pdf
				else
					if (data[a] == 10 && data[a + 1] == 10 && data[a + 2] == 10 && data[a + 3] == -1 && firstMaskValue == PdfFilteredReader.DCTDecode) { // allow
																																							// for
																																							// double
																																							// linefeed
																																							// on
																																							// jpeg
						a = a + 3;
					}
					else
						if (data[a] == 10 && data[a + 1] == 10 && data[a + 2] == -1 && firstMaskValue == PdfFilteredReader.DCTDecode) { // allow for
																																		// double
																																		// linefeed on
																																		// jpeg
							a = a + 2;
						}
						else
							if (data[a] == 10 || data[a] == 13) a++;

				int start = a;

				a--; // move pointer back 1 to allow for zero length stream

				/**
				 * if Length set and valid use it
				 */
				int streamLength = 0;
				int setStreamLength = pdfObject.getInt(PdfDictionary.Length);

				if (debugFastCode) System.out.println(padding + "setStreamLength=" + setStreamLength);

				boolean isValid = false;

				if (setStreamLength != -1) {

					streamLength = setStreamLength;

					// System.out.println("1.streamLength="+streamLength);

					a = start + streamLength;

					if (a < count && data[a] == 13 && (a + 1 < count) && data[a + 1] == 10) a = a + 2;

					// check validity
					if (count > (a + 9) && data[a] == 101 && data[a + 1] == 110 && data[a + 2] == 100 && data[a + 3] == 115 && data[a + 4] == 116
							&& data[a + 5] == 114 && data[a + 6] == 101 && data[a + 7] == 97 && data[a + 8] == 109) {

					}
					else {

						int current = a;
						// check forwards
						if (a < count) {
							while (true) {
								a++;
								if (isValid || a == count) break;

								if (data[a] == 101 && data[a + 1] == 110 && data[a + 2] == 100 && data[a + 3] == 115 && data[a + 4] == 116
										&& data[a + 5] == 114 && data[a + 6] == 101 && data[a + 7] == 97 && data[a + 8] == 109) {

									streamLength = a - start;
									isValid = true;
								}
							}
						}

						if (!isValid) {
							a = current;
							if (a > count) a = count;
							// check backwords
							while (true) {
								a--;
								if (isValid || a < 0) break;

								if (data[a] == 101 && data[a + 1] == 110 && data[a + 2] == 100 && data[a + 3] == 115 && data[a + 4] == 116
										&& data[a + 5] == 114 && data[a + 6] == 101 && data[a + 7] == 97 && data[a + 8] == 109) {
									streamLength = a - start;
									isValid = true;
								}
							}
						}

						if (!isValid) a = current;
					}

					// use correct figure if encrypted
					if (this.decryption != null && this.decryption.getBooleanValue(PDFflags.IS_FILE_ENCRYPTED)) streamLength = setStreamLength;

				}
				else {

					/** workout length and check if length set */
					int end;

					while (true) { // find end

						a++;

						if (a == count) break;
						if (data[a] == 101 && data[a + 1] == 110 && data[a + 2] == 100 && data[a + 3] == 115 && data[a + 4] == 116
								&& data[a + 5] == 114 && data[a + 6] == 101 && data[a + 7] == 97 && data[a + 8] == 109) break;

					}

					end = a - 1;

					if ((end > start)) streamLength = end - start + 1;
				}

				// lose trailing 10s or 13s
				if (streamLength > 1 && !(this.decryption != null && this.decryption.getBooleanValue(PDFflags.IS_FILE_ENCRYPTED))) {// && !isValid){
					int ptr = start + streamLength - 1;

					if (ptr < data.length && ptr > 0
							&& (data[ptr] == 10 || (data[ptr] == 13 && ((pdfObject != null && isJBigEncoded) || (ptr > 0 && data[ptr - 1] == 10))))) {
						streamLength--;
						ptr--;
					}
				}

				/**
				 * read stream into object from memory
				 */
				if (start + streamLength > count) streamLength = count - start;

				// @speed - switch off and investigate
				if (streamLength < 0) return;

				if (streamLength < 0) throw new RuntimeException("Negative stream length " + streamLength + " start=" + start + " count=" + count);
				stream = new byte[streamLength];
				System.arraycopy(data, start, stream, 0, streamLength);

				a = count;
			}

		}

		if (debugFastCode && stream != null) System.out.println(padding + "stream read saved into " + pdfObject);

		if (pdfObject != null) {

			pdfObject.setStream(stream);

			// and decompress now forsome objects
			if (pdfObject.decompressStreamWhenRead()) this.objectReader.readStream(pdfObject, true, true, false,
					pdfObject.getObjectType() == PdfDictionary.Metadata, pdfObject.isCompressedStream(), null);

		}
	}

	private int setNameStringValue(PdfObject pdfObject, int i, byte[] raw, boolean isMap) {

		byte[] stringBytes;

		int keyStart;

		// move cursor to end of last command if needed
		while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != '(')
			i++;

		// move cursor to start of text
		while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32)
			i++;

		// work out if direct (ie /String or read ref 27 0 R
		int j2 = i;
		byte[] arrayData = raw;

		boolean isIndirect = raw[i] != 47 && raw[i] != 40 && raw[i] != 60; // Some /NAME values start (

		boolean startsWithBrace = raw[i] == 40;

		// delete
		// @speed - lose this code once Filters done properly
		/**
		 * just check its not /Filter [/FlateDecode ] or [] or [ /ASCII85Decode /FlateDecode ] by checking next valid char not /
		 */
		boolean isInsideArray = false;
		if (isIndirect) {
			int aa = i + 1;
			while (aa < raw.length && (raw[aa] == 10 || raw[aa] == 13 || raw[aa] == 32))
				aa++;

			if (raw[aa] == 47 || raw[aa] == ']') {
				isIndirect = false;
				i = aa + 1;
				isInsideArray = true;
			}
		}

		if (isIndirect) { // its in another object so we need to fetch

			keyStart = i;

			// move cursor to end of ref
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62) {
				i++;
			}

			// actual value or first part of ref
			int ref = NumberUtils.parseInt(keyStart, i, raw);

			// move cursor to start of generation
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			// get generation number
			keyStart = i;
			// move cursor to end of reference
			while (raw[i] != 10 && raw[i] != 13 && raw[i] != 32 && raw[i] != 47 && raw[i] != 60 && raw[i] != 62)
				i++;

			int generation = NumberUtils.parseInt(keyStart, i, raw);

			// move cursor to start of R
			while (raw[i] == 10 || raw[i] == 13 || raw[i] == 32 || raw[i] == 47 || raw[i] == 60)
				i++;

			if (raw[i] != 82) { // we are expecting R to end ref
				throw new RuntimeException(padding + "2. Unexpected value in file - please send to IDRsolutions for analysis");
			}

			// read the Dictionary data
			arrayData = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed(ref, generation), ref, generation);

			// allow for data in Linear object not yet loaded
			if (arrayData == null) {
				pdfObject.setFullyResolved(false);

				if (debugFastCode) System.out.println(padding + "Data not yet loaded");

				if (LogWriter.isOutput()) LogWriter.writeLog("[Linearized] " + pdfObject.getObjectRefAsString() + " not yet available (13)");

				return raw.length;
			}

			// lose obj at start and roll onto /
			if (arrayData[0] == 47) {
				j2 = 0;
			}
			else {
				j2 = 3;

				while (arrayData[j2] != 47) {
					j2++;
				}
			}
		}

		// lose /
		j2++;

		// allow for no value with /Intent//Filter
		if (arrayData[j2] == 47) return j2 - 1;

		int end = j2 + 1;

		if (isInsideArray) { // values inside []

			// move cursor to start of text
			while (arrayData[j2] == 10 || arrayData[j2] == 13 || arrayData[j2] == 32 || arrayData[j2] == 47)
				j2++;

			int slashes = 0;

			// count chars
			byte lastChar = 0;
			while (true) {
				if (arrayData[end] == ']') break;

				if (arrayData[end] == 47 && (lastChar == 32 || lastChar == 10 || lastChar == 13)) // count the / if gap before
				slashes++;

				lastChar = arrayData[end];
				end++;

				if (end == arrayData.length) break;
			}

			// set value and ensure space gap
			int charCount = end - slashes, ptr = 0;
			stringBytes = new byte[charCount - j2];

			byte nextChar, previous = 0;
			for (int ii = j2; ii < charCount; ii++) {
				nextChar = arrayData[ii];
				if (nextChar == 47) {
					if (previous != 32 && previous != 10 && previous != 13) {
						stringBytes[ptr] = 32;
						ptr++;
					}
				}
				else {
					stringBytes[ptr] = nextChar;
					ptr++;
				}

				previous = nextChar;
			}
		}
		else { // its in data stream directly or (string)

			// count chars
			while (true) {

				if (startsWithBrace) {
					if (arrayData[end] == ')') break;
				}
				else
					if (arrayData[end] == 32 || arrayData[end] == 10 || arrayData[end] == 13 || arrayData[end] == 47 || arrayData[end] == 62) break;

				end++;

				if (end == arrayData.length) break;
			}

			// set value
			int charCount = end - j2;
			stringBytes = new byte[charCount];
			System.arraycopy(arrayData, j2, stringBytes, 0, charCount);

		}

		/**
		 * finally set the value
		 */
		if (isMap) {
			pdfObject.setName(this.PDFkey, StringUtils.getTextString(stringBytes, false));
		}
		else pdfObject.setName(this.PDFkeyInt, stringBytes);

		if (debugFastCode) System.out.println(padding + "String set as =" + new String(stringBytes) + "< written to " + pdfObject);

		// put cursor in correct place (already there if ref)
		if (!isIndirect) i = end - 1;

		return i;
	}

	/**
	 * used by linearization to check object fully fully available and return false if not
	 * 
	 * @param pdfObject
	 */
	public synchronized boolean resolveFully(PdfObject pdfObject) {

		boolean fullyResolved = pdfObject != null;

		if (fullyResolved) {

			byte[] raw;
			if (pdfObject.getStatus() == PdfObject.DECODED) raw = StringUtils.toBytes(pdfObject.getObjectRefAsString());
			else raw = pdfObject.getUnresolvedData();

			// flag now done and flush raw data
			pdfObject.setStatus(PdfObject.DECODED);

			// allow for empty object
			if (raw[0] != 'e' && raw[1] != 'n' && raw[2] != 'd' && raw[3] != 'o' && raw[4] != 'b') {

				int j = 0;

				// allow for [ref] at top level (may be followed by gap
				while (raw[j] == 91 || raw[j] == 32 || raw[j] == 13 || raw[j] == 10)
					j++;

				// get object ref
				int keyStart = j;

				// move cursor to end of reference
				while (raw[j] != 10 && raw[j] != 13 && raw[j] != 32 && raw[j] != 47 && raw[j] != 60 && raw[j] != 62)
					j++;

				int ref = NumberUtils.parseInt(keyStart, j, raw);

				// move cursor to start of generation or next value
				while (raw[j] == 10 || raw[j] == 13 || raw[j] == 32)
					// || data[j]==47 || data[j]==60)
					j++;

				/**
				 * get generation number
				 */
				keyStart = j;

				// move cursor to end of reference
				while (raw[j] != 10 && raw[j] != 13 && raw[j] != 32 && raw[j] != 47 && raw[j] != 60 && raw[j] != 62)
					j++;

				int generation = NumberUtils.parseInt(keyStart, j, raw);

				if (raw[raw.length - 1] == 'R') // recursively validate all child objects
				fullyResolved = resolveFullyChildren(pdfObject, fullyResolved, raw, ref, generation);

				if (fullyResolved) {
					pdfObject.ignoreRecursion(false);
					ObjectDecoder objDecoder = new ObjectDecoder(this.objectReader);
					objDecoder.readDictionaryAsObject(pdfObject, j, raw);

					// if(!pdfObject.isFullyResolved())
					// fullyResolved=false;
				}
			}
		}

		return fullyResolved;
	}

	private boolean resolveFullyChildren(PdfObject pdfObject, boolean fullyResolved, byte[] raw, int ref, int generation) {

		pdfObject.setRef(new String(raw));
		pdfObject.isDataExternal(true);

		byte[] pageData = this.objectReader.readObjectAsByteArray(pdfObject, this.objectReader.isCompressed(ref, generation), ref, generation);

		// allow for data in Linear object not yet loaded
		if (pageData == null) {
			pdfObject.setFullyResolved(false);
			fullyResolved = false;
		}
		else {
			pdfObject.setStatus(PdfObject.UNDECODED_DIRECT);
			pdfObject.setUnresolvedData(pageData, PdfDictionary.Linearized);
			pdfObject.isDataExternal(true);

			if (!resolveFully(pdfObject)) pdfObject.setFullyResolved(false);
		}

		return fullyResolved;
	}

	/**
	 * read object setup to contain only ref to data
	 * 
	 * @param pdfObject
	 */
	public void checkResolved(PdfObject pdfObject) {

		if (pdfObject != null && pdfObject.getStatus() != PdfObject.DECODED) {

			byte[] raw = pdfObject.getUnresolvedData();

			// flag now done and flush raw data
			pdfObject.setStatus(PdfObject.DECODED);

			// allow for empty object
			if (raw[0] == 'e' && raw[1] == 'n' && raw[2] == 'd' && raw[3] == 'o' && raw[4] == 'b') {
				// empty object
			}
			else
				if (raw[0] == 'n' && raw[1] == 'u' && raw[2] == 'l' && raw[3] == 'l') {
					// null object
				}
				else { // we need to ref from ref elsewhere which may be indirect [ref], hence loop

					String objectRef = pdfObject.getObjectRefAsString();

					// allow for Color where starts [/ICCBased 2 0 R so we get the ref if present
					if (raw[0] == '[') {

						// scan along to find number
						int ptr = 0, len = raw.length;
						for (int jj = 0; jj < len; jj++) {

							if (raw[jj] >= '0' && raw[jj] <= '9') {
								ptr = jj;
								jj = len;
							}
						}

						// check first non-number is R
						int end = ptr;
						while ((raw[end] >= '0' && raw[end] <= '9') || raw[end] == ' ' || raw[end] == 10 || raw[end] == 13 || raw[end] == 9)
							end++;
						// and store if it is a ref
						if (raw[end] == 'R') pdfObject.setRef(new String(raw, ptr, len - ptr));

					}
					else
						if (raw[raw.length - 1] == 'R') pdfObject.setRef(new String(raw));

					ObjectDecoder objDecoder = new ObjectDecoder(this.objectReader);
					objDecoder.readDictionaryFromRefOrDirect(-1, pdfObject, objectRef, 0, raw, -1);

				}
		}
	}

	/**
	 * set end if not end of data stream
	 */
	public void setEndPt(int dataPointer) {
		this.endPt = dataPointer;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy