/*
 * ===========================================
 * Java Pdf Extraction Decoding Access Library
 * ===========================================
 *
 * Project Info:  http://www.jpedal.org
 * Project Lead:  Mark Stephens (mark@idrsolutions.com)
 *
 * (C) Copyright 2003, 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 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
    General Public License for more details.

    You should have received a copy of the GNU 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


 *
 * ---------------
 * PdfDecoder.java
 * ---------------
 * (C) Copyright 2005, by IDRsolutions and Contributors.
 *
 * Original Author:  Mark Stephens (mark@idrsolutions.com)
 * Contributor(s):
 *
 */
package org.jpedal;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Pageable;
import java.awt.print.Paper;
import java.awt.print.Printable;
import java.awt.print.PrinterException;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringBufferInputStream;
import java.net.JarURLConnection;
// <start-13>
import java.net.URI;
import javax.print.attribute.SetOfIntegerSyntax;
// <end-13>
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.RepaintManager;
import javax.swing.Timer;
import javax.xml.parsers.DocumentBuilderFactory;


// <start-forms>
import org.jpedal.objects.acroforms.AcroRenderer;
import org.jpedal.objects.acroforms.DefaultAcroRenderer;
import org.jpedal.objects.acroforms.DefaultAnnotRenderer;
import org.jpedal.objects.acroforms.FormFactory;
import org.jpedal.objects.acroforms.FormStream;
// <end-forms>
import org.jpedal.color.ColorSpaces;

import org.jpedal.examples.simpleviewer.utils.BrowserLauncher;
import org.jpedal.examples.simpleviewer.utils.SwingWorker;
import org.jpedal.exception.PdfException;
import org.jpedal.exception.PdfFontException;
import org.jpedal.gui.Hotspots;
import org.jpedal.io.ColorSpaceConvertor;
import org.jpedal.io.ObjectStore;
import org.jpedal.io.PdfObjectReader;
import org.jpedal.io.StatusBar;
import org.jpedal.objects.PageLines;
import org.jpedal.objects.PageLookup;
import org.jpedal.objects.PdfAnnots;
import org.jpedal.objects.PdfFileInformation;
import org.jpedal.objects.PdfFormData;
import org.jpedal.objects.PdfPageData;
import org.jpedal.objects.outlines.OutlineData;
import org.jpedal.utils.LogWriter;
import org.jpedal.utils.Strip;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.idrsolutions.pdf.parser.PdfStreamDecoder;
import com.idrsolutions.pdf.renderer.DynamicVectorRenderer;

/**
 * Provides an object to decode pdf files and provide a rasterizer if required -
 * Normal usage is to create instance of PdfDecoder and access via public
 * methods. Examples showing usage in org.jpedal.examples - Inherits indirectly
 * from JPanel so can be used as a standard Swing component -
 * 
 * Extends other classes to separate out GUI and business logic but should be
 * regarded as ONE object and PdfPanel should not be instanced - We recommend
 * you access JPedal using only public methods listed in API
 */
final public class PdfDecoder extends PdfPanel implements Printable, Pageable {

	/** version number of software */
	public static final String version = "EO-2.72b08";

	int test = 0;

	/** flag for xfa form */
	private boolean isXFA = false;

	/** flag to show if on mac so we can code around certain bugs */
	public static boolean isRunningOnMac = false;

	/** provide print debug feature */
	private boolean debugPrint = false;


	private boolean stopPrinting = false;


	/** PDF version */
	private String pdfVersion = "";

	public static boolean isDraft = true;

	private boolean useForms = true;

	/** direct graphics 2d to render onto */
	private Graphics2D g2 = null;


	/** provide access to pdf file objects */
	private PdfObjectReader currentPdfFile;

	/** flag to show embedded fonts present */
	private boolean hasEmbeddedFonts = false;

	/** list of fonts for decoded page */
	private String fontsInFile = "";

	/** dpi for final images */
	public static int dpi = 72;

	/**
	 * flag to tell software to embed x point after each character so we can
	 * merge any overlapping text together
	 */
	public static boolean embedWidthData = false;

	/** page lookup table using objects as key */
	private PageLookup pageLookup = new PageLookup();

	/** flag to stop multiple access to background decoding */
	private boolean isBackgroundDecoding = false;

	/** store outline data extracted from pdf */
	private OutlineData outlineData = null;

	/** store outline data object reference so can be read if needed */
	private Object outlineObject = null;

	/** store information object */
	private String XMLObject;

	/** flag to show outline */
	private boolean hasOutline = false;

	/** actual page range to print */
	private int start = 0, end = -1;


	/** printing object */
	PdfStreamDecoder currentPrintDecoder = null;

	/** last page printed */
	private int lastPrintedPage = -1;

	/** flag to show extraction mode includes any text */
	public static final int TEXT = 1;

	/** flag to show extraction mode includes original images */
	public static final int RAWIMAGES = 2;

	/** flag to show extraction mode includes final scaled/clipped */
	public static final int FINALIMAGES = 4;

	/** undocumented flag to allow shape extraction */
	protected static final int PAGEDATA = 8;

	/** flag to show extraction mode includes final scaled/clipped */
	public static final int RAWCOMMANDS = 16;

	/** flag to show extraction of clipped images at highest res */
	public static final int CLIPPEDIMAGES = 32;

	/** flag to show extraction of clipped images at highest res */
	public static final int TEXTCOLOR = 64;

	/** flag to show extraction of raw cmyk images */
	public static final int CMYKIMAGES = 128;

	/** flag to show extraction of xforms metadata */
	public static final int XFORMMETADATA = 256;

	/** flag to show render mode includes any text */
	public static final int RENDERTEXT = 1;

	/** flag to show render mode includes any images */
	public static final int RENDERIMAGES = 2;

	// <end-viewer>

	/** reference object for annotations */
	private String annotObject = null;

	/** flag to show if form data contained in current file */
	private boolean isForm = false;


	/** current extraction mode */
	private static int extractionMode = 7;

	/** current render mode */
	private static int renderMode = 7;



	/** decodes page */
	private PdfStreamDecoder current;
	

	/** holds pdf id (ie 4 0 R) which stores each object */
	private Map pagesReferences = new Hashtable();

	/** global resources */
	private Map globalRes;

	/** used to remap fonts onto truetype fonts (set internally) */
	public static Map fontSubstitutionTable = null;

	/** used to remap fonts onto truetype fonts (set internally) */
	public static Map fontSubstitutionLocation = new Hashtable();

	/** used to remap fonts onto truetype fonts (set internally) */
	public static Map fontSubstitutionAliasTable = new Hashtable();

	/**
	 * flag to show if there must be a mapping value (program exits if none
	 * found)
	 */
	public static boolean enforceFontSubstitution = false;

	/** flag to show user wants us to display printable area when we print */
	private boolean showImageable = false;

	/** font to use in preference to Lucida */
	public static String defaultFont = null;

	/** holds pageformats */
	private Map pageFormats = new Hashtable();

	final private static String separator = System
			.getProperty("file.separator");

	/** flag to show if data extracted as text or XML */
	private static boolean isXMLExtraction = true;

	// stores default page sizes at each level so we can lookup
	private Map globalMediaValues = new Hashtable();

	/**
	 * used by Storypad to include images in PDFData)
	 */
	private boolean includeImages;

	/** interactive status Bar */
	private StatusBar statusBar = null;

	/**
	 * flag to say if java 1.3 version should be used for JPEG conversion (new
	 * JPEG bugs in Suns 1.4 code)
	 */
	public static boolean use13jPEGConversion = false;

	/**
	 * uses the scaling applied to the page unless over 1. In this case it uses
	 * a value of 1
	 */
	private boolean usePageScaling = false;

	/** tells JPedal to display screen using hires images */
	private boolean useHiResImageForDisplay = false;

	/** flag used to show if printing worked */
	private boolean operationSuccessful = true;

	/** Any printer errors */
	private String pageErrorMessages = "";

	private String filename;

	private ObjectStore backgroundObjectStoreRef = new ObjectStore();

	// <start-13>
	private SetOfIntegerSyntax range;

	// <end-13>

	final private boolean oldPrint = false;

	public static boolean flattenDebug = false;

	private static final boolean useXFAformsCheck = false;

	/**
	 * printing mode using inbuilt java fonts and getting java to rasterize
	 * fonts using Java font if match found (added to get around limitations in
	 * PCL printing via JPS)
	 */
	public static final int TEXTGLYPHPRINT = 1;

	/**
	 * printing mode using inbuilt java fonts and getting java to rasterize
	 * fonts using Java font if match found (added to get around limitations in
	 * PCL printing via JPS) - this is the default off setting
	 */
	public static final int NOTEXTPRINT = 0;

	/**
	 * printing mode using inbuilt java fonts and getting java to rasterize
	 * fonts using Java font if match found (added to get around limitations in
	 * PCL printing via JPS) - this is the default off setting
	 */
	public static final int TEXTSTRINGPRINT = 2;


	/**
	 * work out machine type so we can call OS X code to get around Java bugs.
	 */
	static {

		/**
		 * see if mac
		 */
		try {
			String name = System.getProperty("os.name");
			if (name.equals("Mac OS X"))
				PdfDecoder.isRunningOnMac = true;
		} catch (Exception e) {
			e.printStackTrace();
		}


		// <end-os>
		// <start-13>

		/***********************************************************************
		 * limits for demo
		 * 
		 * try {
		 * 
		 * java.util.Calendar now = java.util.Calendar.getInstance(); //test
		 * expires if
		 * ((now.get(java.util.Calendar.YEAR)==2004)&(now.get(java.util.Calendar.MONTH)<8)){
		 * }else{ LogWriter.writeLog("This has now expired");
		 * System.out.println("This has now expired"); //System.exit(1);
		 *  } }catch(Exception e){
		 *  } /
		 **********************************************************************/
		// <end-13>
		// <end-os>
	}

	/**
	 * allows a number of fonts to be mapped onto an actual font and provides a
	 * way around slightly differing font naming when substituting fonts - So if
	 * arialMT existed on the target machine and the PDF contained arial and
	 * helvetica (which you wished to replace with arialmt), you would use the
	 * following code -
	 * 
	 * String[] aliases={"arial","helvetica"};
	 * currentPdfDecoder.setSubstitutedFontAliases("arialmt",aliases); -
	 * 
	 * comparison is case-insensitive and file type/ending should not be
	 * included - For use in conjunction with -DTTfontDirs options which allows
	 * user to pass a set of comma separated directories with Truetype fonts
	 * (directories do not need to exist so can be multi-platform setting)
	 * 
	 */
	public void setSubstitutedFontAliases(String fontFileName, String[] aliases) {

		if (aliases != null) {

			String name = fontFileName.toLowerCase(), alias = null;
			int count = aliases.length;
			for (int i = 0; i < count; i++) {
				alias = aliases[i].toLowerCase();
				if (!alias.equals(name))
					fontSubstitutionAliasTable.put(alias, name);
			}
		}
	}

	/**
	 * takes a comma separated list of font directories and add to substitution
	 * 
	 * @throws FileNotFoundException
	 */
	private String addTTFonts(String fontDirs, String failed) {

		StringTokenizer fontPaths = new StringTokenizer(fontDirs, ",");

		while (fontPaths.hasMoreTokens()) {

			String fontPath = fontPaths.nextToken();

			if (!fontPath.endsWith("/") & !fontPath.endsWith("\\"))
				fontPath = fontPath + separator;

			LogWriter.writeLog("Looking in " + fontPath + " for TT fonts");

			addTTDir(fontPath, failed);
		}

		return failed;
	}

	/**
	 * turns off the viewable area, scaling the page back to original scaling
	 * <br>
	 * <br>
	 * NOT RECOMMENDED FOR GENERAL USE (this has been added for a specific
	 * client and we have found it can be unpredictable on some PDF files).
	 */
	public void resetViewableArea() {

		viewableArea = null;
		// @fontHandle currentDisplay.setOptimiseDrawing(true);
		setPageRotation(displayRotation);
		repaint();
	}

	/**
	 * allows the user to create a viewport within the displayed page, the
	 * aspect ratio is keep for the PDF page <br>
	 * <br>
	 * Passing in a null value is the same as calling resetViewableArea()
	 * 
	 * <br>
	 * <br>
	 * The viewport works from the bottom left of the PDF page <br>
	 * The general formula is <br>
	 * (leftMargin, <br>
	 * bottomMargin, <br>
	 * pdfWidth-leftMargin-rightMargin, <br>
	 * pdfHeight-bottomMargin-topMargin)
	 * 
	 * <br>
	 * <br>
	 * NOT RECOMMENDED FOR GENERAL USE (this has been added for a specific
	 * client and we have found it can be unpredictable on some PDF files).
	 * 
	 * <br>
	 * <br>
	 * The viewport will not be incorporated in printing <br>
	 * <br>
	 * Throws PdfException if the viewport is not totally enclosed within the
	 * 100% cropped pdf
	 */
	public AffineTransform setViewableArea(Rectangle viewport)
			throws PdfException {

		if (viewport != null) {

			double x = viewport.getX();
			double y = viewport.getY();
			double w = viewport.getWidth();
			double h = viewport.getHeight();

			// double crx = pageData.getCropBoxX(pageNumber);
			// double cry = pageData.getCropBoxY(pageNumber);
			double crw = pageData.getCropBoxWidth(pageNumber);
			double crh = pageData.getCropBoxHeight(pageNumber);

			// throw exception if viewport cannot fit in cropbox
			if (x < 0 || y < 0 || (x + w) > crw || (y + h) > crh) {
				throw new PdfException(
						"Viewport is not totally enclosed within displayed panel.");
			}

			// if viewport exactlly matches the cropbox
			if (crw == w && crh == h) {
			} else {// else work out scaling ang apply

				viewableArea = viewport;
				currentDisplay.setOptimiseDrawing(false);
				setPageRotation(displayRotation);
				repaint();
			}
		} else {
			resetViewableArea();
		}

		return viewScaling;
	}

	/**
	 * takes a String[] of font directories and adds to substitution - Can just
	 * be called for each JVM - Should be called before file opened - this
	 * offers an alternative to the call -DTTfontDirs - Passing a null value
	 * flushes all settings
	 * 
	 * @return String which will be null or list of directories it could not
	 *         find
	 */
	public static String setTTFontDirs(String[] fontDirs) {

		String failed = null;

		try {
			if (fontDirs == null) { // idiot safety test
				LogWriter.writeLog("Null font parameter passed");
				PdfDecoder.fontSubstitutionAliasTable.clear();
				PdfDecoder.fontSubstitutionLocation.clear();
				PdfDecoder.fontSubstitutionLocation.clear();
			} else {

				int count = fontDirs.length;

				for (int i = 0; i < count; i++) {

					String fontPath = fontDirs[i];

					// allow for 'wrong' separator
					if (!fontPath.endsWith("/") & !fontPath.endsWith("\\"))
						fontPath = fontPath + separator;

					System.out.println("Looking in " + fontPath
							+ " for TT fonts");
					LogWriter.writeLog("Looking in " + fontPath
							+ " for TT fonts");

					failed = addTTDir(fontPath, failed);
				}
			}
		} catch (Exception e) {
			LogWriter.writeLog("Unable to run setTTFontDirs " + e.getMessage());
		}

		return failed;
	}

	/**
	 * add a truetype font directory and contents to substitution
	 */
	private static String addTTDir(String fontPath, String failed) {

		if (PdfDecoder.fontSubstitutionTable == null)
			fontSubstitutionTable = new HashMap();

		String[] types = { "/TrueType" };

		File currentDir = new File(fontPath);

		if ((currentDir.exists()) && (currentDir.isDirectory())) {

			String[] files = currentDir.list();

			if (files != null) {
				int count = files.length;

				for (int i = 0; i < count; i++) {
					String currentFont = files[i];

					if (currentFont.toLowerCase().endsWith(".ttf")) {
						// see if root dir exists
						InputStream in = null;
						try {
							in = new FileInputStream(fontPath + currentFont);

						} catch (FileNotFoundException e) {
							e.printStackTrace();
						}

						System.out.println(in + " " + currentFont);

						// if it does, add
						if (in != null) {

							String fontName;

							int pointer = currentFont.indexOf(".");
							if (pointer == -1)
								fontName = currentFont;
							else
								fontName = currentFont.substring(0, pointer);

							fontSubstitutionTable.put(fontName.toLowerCase(),
									types[0]);
							fontSubstitutionLocation.put(
									fontName.toLowerCase(), fontPath
											+ currentFont);
							LogWriter.writeLog("Added truetype font "
									+ fontName + " path=" + fontPath
									+ currentFont);
							System.out.println("Added truetype font "
									+ fontName + " path=" + fontPath
									+ currentFont);

						} else {
							LogWriter.writeLog("No fonts found at " + fontPath);
						}
					}

				}
			}
		} else {
			if (failed == null) {
				failed = fontPath;
			} else {
				failed = failed + "," + fontPath;
			}
		}

		return failed;
	}

	// <end-viewer>


	/**
	 * Recommend way to create a PdfDecoder if no rendering of page may be
	 * required in Enterprise version <br>
	 * Otherwise use PdfDecoder()
	 * 
	 * @param newRender
	 *            flag to show if pages being rendered for JPanel or extraction
	 */
	public PdfDecoder(boolean newRender) {

		objectStoreRef = new ObjectStore();

		/** get local handles onto flag passed in */
		this.renderPage = newRender;

		setLayout(null);

		startup();
	}

	/**
	 * Not part of API - internal IDR method
	 */
	public PdfDecoder(int mode, boolean newRender) {

		objectStoreRef = new ObjectStore();

		/** get local handles onto flag passed in */
		this.renderPage = newRender;

		setLayout(null);

		startup();
	}

	/**
	 * set images scaling to use faster and less accurrate scaling for images
	 * public void useBicubicImageScaling(){ PdfDecoder.isDraft=false; }
	 */

	/**
	 * 
	 */
	private void startup() {

		// <start-forms>
		formsAvailable = PdfStreamDecoder.isFormSupportAvailable();

		if (this.formsAvailable) {
			currentFormRenderer = new DefaultAcroRenderer();

			currentAnnotRenderer = new DefaultAnnotRenderer();
		}
		// <end-forms>


		// needs to be set so we can over-ride
		if (renderPage) {
			setToolTipText("image preview");

			// initialisation on font
			highlightFont = new Font("Lucida", Font.BOLD, size);

			setPreferredSize(new Dimension(100, 100));
		}
	}

	public static boolean runningAsFreeApplication() {
		return runningAsFreeApplication;
	}

	private static boolean runningAsFreeApplication = false;

	/**
	 * Recommend way to create a PdfDecoder for renderer only viewer (not
	 * recommended for server extraction only processes)
	 */
	public PdfDecoder() {

		objectStoreRef = new ObjectStore();

		this.renderPage = true;

		setLayout(null);

		startup();
	}

	/**
	 * convenience method to close the current PDF file
	 */
	final public void closePdfFile() {

		// ensure no previous file still being decoded
		stopDecoding();


		if (currentPdfFile != null)
			currentPdfFile.closePdfFile();

		currentPdfFile = null;

		objectStoreRef.flush();

	}

	/**
	 * convenience method to get the PDF data as a byte array - works however
	 * file was opened.
	 * 
	 * @return byte array containing PDF file
	 */
	final public byte[] getPdfBuffer() {

		byte[] buf = null;
		if (currentPdfFile != null)
			buf = currentPdfFile.getPdfBuffer();

		return buf;
	}


	/**
	 * <B>Not part of API</B> provide method for outside class to get data
	 * object containing information on the page for calculating grouping <br>
	 * Please note: Structure of PdfPageData is not guaranteed to remain
	 * constant. Please contact IDRsolutions for advice.
	 * 
	 * @return PdfPageData object
	 * @deprecated from 2.50
	 */
	final public PdfPageData getPdfBackgroundPageData() {
		return pageData;
	}

	/** flag to show if PDF document contains an outline */
	final public boolean hasOutline() {
		return hasOutline;
	}

	/** return a DOM document containing the PDF Outline object as a DOM Document */
	final public Document getOutlineAsXML() {

		if (outlineData == null) {
			if (outlineObject != null) {

				/**/
				try {
					outlineData = new OutlineData(pageCount);
					outlineData.readOutlineFileMetadata(outlineObject,
							currentPdfFile, pageLookup);

				} catch (Exception e) {
					System.out.println("Exception " + e + " accessing outline "
							+ outlineObject);
					outlineData = null;
				}
				/***/

			}
		}
		return outlineData.getList();
	}

	/**
	 * <B>Not part of API</B> provide method for outside class to get data
	 * object containing information on the page for calculating grouping <br>
	 * Please note: Structure of PdfPageData is not guaranteed to remain
	 * constant. Please contact IDRsolutions for advice.
	 * 
	 * @return PdfPageData object
	 */
	final public PdfPageData getPdfPageData() {
		return pageData;
	}

	/**
	 * set page range (inclusive) - 
	 * If end is less than start it will print them
	 * backwards (invalid range will throw PdfException)
	 * @throws PdfException 
	 * 
	 */
	public void setPagePrintRange(int start, int end) throws PdfException {
		this.start = start;
		this.end = end;
		
		//all returns huge number not page end range
		if(end==2147483647)
			end=getPageCount();
		
		//if actually backwards, reverse order
		if(start>end){
			int tmp=start;
			start=end;
			end=tmp;
		}
		if((start<1)||(end<1)||(start>this.pageCount)||(end>this.pageCount))
			throw new PdfException("Invalid page range with "+start+" "+end);
		
	}

	// <start-13>
	/**
	 * set inclusive range to print (see SilentPrint.java and SimpleViewer.java
	 * for sample print code (invalid range will throw PdfException)
	 * @throws PdfException 
	 * @throws PdfException 
	 */
	public void setPagePrintRange(SetOfIntegerSyntax range) throws PdfException {
		this.range = range;
		this.start = range.next(0); // find first

		// find last
		int i = start;
		this.end = start;
		if(range.contains(2147483647)) //allow for all returning largest int
			end=getPageCount();
		else{
			while (range.next(i) != -1)
			i++;
			end = i;
		}
		
		//if actually backwards, reverse order
		if(start>end){
			int tmp=start;
			start=end;
			end=tmp;
		}
		
		if((start<1)||(end<1)||(start>this.pageCount)||(end>this.pageCount))
			throw new PdfException("Invalid page range with "+start+" "+end);
		
	}

	// <end-13>

	/**
	 * tells program to try and use Java's font printing if possible as work
	 * around for issue with PCL printing - values are PdfDecoder.TEXTGLYPHPRINT
	 * (use Java to rasterize font if available) PdfDecoder.TEXTSTRINGPRINT(
	 * print as text not raster - fastest option) PdfDecoder.NOTEXTPRINT
	 * (default - highest quality)
	 */
	public void setTextPrint(int textPrint) {
		this.textPrint = textPrint;
	}

	/** flag to use Java's inbuilt font renderer if possible */
	private int textPrint = 0;

	/** size above which objects stored on disk (-1 is off) */
	private int minimumCacheSize = -1;

	/** return any messages on decoding */
	private String decodeStatus = "";

	/** if user chooses view, mode show if we have worked out sizes for pages */
	private boolean viewCalculated;

	/** current print page or -1 if finished */
	private int currentPrintPage = 0;

	private boolean imagesProcessedFully;

	/**
	 * implements the standard Java printing functionality if start <end it will
	 * print it in reverse
	 * 
	 * @param graphics -
	 *            object page rendered onto
	 * @param pf
	 *            PageFormat object used to print
	 * @param page -
	 *            current page index (less 1 so start at page 0)
	 * @return int Printable.PAGE_EXISTS or Printable.NO_SUCH_PAGE
	 * @throws PrinterException
	 */
	public int print(Graphics graphics, PageFormat pf, int page)
			throws PrinterException {


		//exit if requested
		if(stopPrinting){
			stopPrinting=false;
			start=0;
			end=0;
			return Printable.NO_SUCH_PAGE;
		}

		if (debugPrint)
			System.out.println(page + " print called with values start="
					+ start + " end=" + end + " graphics=" + graphics);

		int i = Printable.PAGE_EXISTS;

		// <start-13>
		// stop pages not in range printing
		if ((this.range != null) && (!range.contains(start + page))) {
			if (debugPrint)
				System.out.println("not in range");
			if ((page < start) || (page + 1 > end))
				return Printable.NO_SUCH_PAGE;
			else
				return i;
		}
		// <end-13>

		String printAnnotObject = null;
		PdfAnnots printAnnots = null;

		if (debugPrint)
			System.out.println("Passed range test");

		try {

			double scaling = 1;

			/**
			 * set scaling value - pageScaling is deprecated but left in for
			 * moment to provide backwards compatability
			 */
			if (usePageScaling)
				scaling = this.scaling;
			
			// increase index to match out page numbers- JavaPrint starts at 0
			// also allow for reverse
			int lastPage=end,firstPage=start;
			
			if((end!=-1)&&(end<start)){
				firstPage=end;
				lastPage=start;
				page = start - page;
			}else
				page = start + page;
			
			if (end == -1)
				page++;
			
			currentPrintPage = page;

			// make sure in range
			if ((page > pageCount) | ((end != -1) & (page > lastPage))) {

				currentPrintPage = -1;

				if (debugPrint)
					System.out.println("no such page");

				return Printable.NO_SUCH_PAGE;
			}

			if ((end == -1) | ((page >= firstPage) & (page <= lastPage))) {

				if (debugPrint)
					System.out.println("page in range");

				operationSuccessful = true;
				pageErrorMessages = "";

				try {

					/**
					 * setup for decoding page
					 */

					/** get pdf object id for page to decode */
					String currentPageOffset = (String) pagesReferences.get(new Integer(page));

					if ((currentPageOffset != null)) {

						if (debugPrint)
							System.out.println("currentPageOffset="+ currentPageOffset);

						if (currentPdfFile == null)
							throw new PrinterException("File not open - did you call closePdfFile() inside a loop and not reopen");

						/** read page or next pages */
						Map values = currentPdfFile.readObject(currentPageOffset, false, null);

						/**
						 * read the Annotations for the page we have found lots
						 * of issues with annotations so trap errors
						 */
						try {
							// copied from decodepage to print the annotations
							printAnnotObject = currentPdfFile.getValue((String) values.get("Annots"));

							// <start-forms>
							if (formsAvailable) {
								if ((renderPage) && (printAnnotObject != null)&& (showAnnotations)) {

									printAnnots = new PdfAnnots(currentPdfFile,pageLookup);
									printAnnots.readAnnots(printAnnotObject);

									currentAnnotRenderer.init(printAnnots,insetW, insetH, pageData,currentPdfFile);

								}
							}
							// <end-forms>

						} catch (Exception e1) {
							LogWriter.writeLog("[PDF] " + e1+ " with annotation");
						}

						/** get information for the page */
						String value = (String) values.get("Contents");

						/** flush annotations */
						if (printHotspots != null)
							printHotspots.flushAnnotationsDisplayed();

						if (value != null) {

							/** decode page only on first pass */
							if ((oldPrint) || (lastPrintedPage != page)) {
								
								currentPrintDecoder = new PdfStreamDecoder(true);
								currentPrintDecoder.setTextPrint(textPrint);

								/** the ObjectStore for this file for printing */
								ObjectStore objectPrintStoreRef = new ObjectStore();

								currentPrintDecoder.optimiseDisplayForPrinting();
								currentPrintDecoder.setStore(objectPrintStoreRef);

								Map res = currentPdfFile.getSubDictionary(values.get("Resources"));

								try {
									currentPrintDecoder.init(true, renderPage,renderMode, 0, pageData, page,null, currentPdfFile, globalRes,res);
								} catch (PdfException ee) {
									throw new PdfFontException(ee.getMessage());
								}
							}

							/**
							 * get imageble area - ie box we print into
							 */
							int iX = (int) pf.getImageableX();
							int iY = (int) pf.getImageableY();
							int iW = (int) pf.getImageableWidth() - 1;
							int iH = (int) pf.getImageableHeight() - 1;



                            // graphics.setClip(imageX,imageY,imageWidth,imageHeight);

							// setup transformations
							AffineTransform printScaling = new AffineTransform();

							/**
							 * get media box - ie page size
							 */
							int mediaW = pageData.getMediaBoxWidth(page);
							int mediaH = pageData.getMediaBoxHeight(page);
							int mediaX = pageData.getMediaBoxX(page);
							int mediaY = pageData.getMediaBoxY(page);

							/**
							 * copy of crop values for clipping
							 */
							int clipW,clipH,clipX,clipY;

							/**
							 * get crop box and rotation
							 */
							int crx = clipX = pageData.getCropBoxX(page);
							int cry = clipY = pageData.getCropBoxY(page);
							int crw = clipW = pageData.getCropBoxWidth(page) + 1;
							int crh = clipH = pageData.getCropBoxHeight(page) + 1;
							
							// put border on printable area if requrested
							if (showImageable) {

								Rectangle printableArea = new Rectangle(iX,iY, iW, iH);
								
								System.out.println("image=" + iX + " "+ iY + " " + iW + " "+ iH);
								Graphics2D printableG2 = (Graphics2D) graphics;
								printableG2.setColor(Color.red);
								printableG2.fill(printableArea);
								printableG2.setColor(Color.white);
								printableG2.draw(printableArea);
								printableG2.draw(new Line2D.Float(iX,iY, iX + iW, iY+ iH));
								printableG2.draw(new Line2D.Float(iX,iY + iH, iX+ iW, iY));
							}

							double pScale = 1.0;

							/**
							 * size of the page we are printing
							 */
							int print_x_size = crw, print_y_size = crh;

							//old scaling code (should not be used)
							if (usePageScaling) { 
								/** avoid oversize scaling shrinking page */
								print_x_size = (int) ((crw) * scaling);
								print_y_size = (int) ((crh) * scaling);

								if (((print_x_size > iW) | (print_y_size > iH))) {
									print_x_size = crw;
									print_y_size = crh;
									scaling = 1;
								}
							}

							/**
							 * workout scaling factor and use the smaller scale
							 * factor
							 */
							double pageScaleX = (double) iW/ print_x_size;
							double pageScaleY = (double) iH/ print_y_size;
							double newScaling = 0;
							boolean scaledOnX=true;
							if (pageScaleX < pageScaleY)
								newScaling = pageScaleX;
							else{
								scaledOnX=false;
								newScaling = pageScaleY;
							}
							if (usePageScaling) {

								pScale = newScaling;
								
								//old scaling code (should not beused)
								if (((print_x_size > iW) | (print_y_size > iH))) { 

									/** adjust settings to fit page */
									print_x_size = (int) (print_x_size * pScale);
									print_y_size = (int) (print_y_size * pScale);
								}

							} else { // new scaling code
								if (pageScalingMode == PAGE_SCALING_NONE) {
									// do nothing
								} else if (pageScalingMode == PAGE_SCALING_FIT_TO_PRINTER_MARGINS) {// new
																								
									pScale = newScaling;
									/** adjust settings to fit page */

								} else if (pageScalingMode == PAGE_SCALING_REDUCE_TO_PRINTER_MARGINS) {// new
									if (newScaling < 1)
										pScale = newScaling;
								}

								/** center image in middle of page */
								int dx = (int) (((iW) - (crw * pScale)) / 2);
								if ((dx < 0) && (pageScalingMode != PAGE_SCALING_NONE))
									dx = 0;
								int dy = (int) (((iH + iY) - (crh * pScale)) / 2);
								if ((dy < 0) && (pageScalingMode != PAGE_SCALING_NONE))
									dy = 0;
								
								/**correctly align*/
								if ((pageScalingMode == PAGE_SCALING_FIT_TO_PRINTER_MARGINS)||(pScale<1)){
									if(scaledOnX)
										dx=0;
									else
										dy=0;
								}
								
								printScaling.translate(dx, dy);
								
							}

							printScaling.translate(-(crx * pScale),(cry * pScale));
							printScaling.translate(iX, iY);
							
							/**
							 * set appropiate scaling
							 */
							if (pScale != 1) {
								if (this.usePageScaling) {
									printScaling.translate(print_x_size,print_y_size);
									printScaling.scale(1, -1);
									printScaling.translate(-print_x_size, 0);
								} else {
									printScaling.translate(print_x_size* pScale, print_y_size* pScale);
									printScaling.scale(1, -1);
									printScaling.translate(-print_x_size* pScale, 0);
								}
								printScaling.scale(pScale, pScale);

							} else {
								printScaling.translate(print_x_size,print_y_size);
								printScaling.scale(1, -1);
								printScaling.translate(-print_x_size, 0);
								printScaling.scale(scaling, scaling);// &&
							}

							/** reassign of crop values for clipping */
							crx = clipX;
							cry = clipY;
							crw = clipW;
							crh = clipH;

							// turn off double buffering
							RepaintManager currentManager = RepaintManager.currentManager(this);
							currentManager.setDoubleBufferingEnabled(false);

							Graphics2D g2 = (Graphics2D) graphics;

							g2.setRenderingHints(ColorSpaces.hints);
							g2.transform(printScaling);

							if (showImageable) {
								System.out.println("media=" + mediaX + " "
										+ mediaY + " " + mediaW + " " + mediaH);
								g2.setColor(Color.black);
								g2.draw(new Rectangle(mediaX, mediaY, mediaW,
										mediaH));
								g2.drawLine(mediaX, mediaY, mediaW + mediaX,
										mediaH + mediaY);
								g2.drawLine(mediaX, mediaH + mediaY, mediaW
										+ mediaX, mediaY);
								System.out.println("crop=" + crx + " " + cry
										+ " " + crw + " " + crh);
								g2.setColor(Color.blue);
								g2.draw(new Rectangle(crx, cry, crw, crh));
								g2.drawLine(crx, cry, crw + crx, crh + cry);
								g2.drawLine(crx, crh + cry, crw + crx, cry);
							} else {
								g2.clip(new Rectangle(crx, cry, crw, crh));
							}

							/**
							 * pass in values as needed for patterns
							 */
							currentPrintDecoder.getPdfStreamDecoder().setScalingValues(crx, crh + cry,(float) pScale);

							if (oldPrint) {
								/** decode and print in 1 go */
								currentPrintDecoder.setDirectRendering(g2);
							}

							/** decode page only on first pass */
							if (oldPrint || (lastPrintedPage != page)) {

								if (debugPrint)
									System.out.println("About to decode stream");

								try {
									currentPrintDecoder.decodePageContent(value, 0, 0, null);
								} catch (PdfException e2) {
									e2.printStackTrace();
									throw new PrinterException(e2.getMessage());
								}

								lastPrintedPage = page;

								if (debugPrint)
									System.out.println("Decoded stream");
							}

							if (debugPrint)
								System.out.println("About to print stream");

							/**
							 * print
							 */
							if (!oldPrint)
								currentPrintDecoder.print(g2, null,showImageable);

							if (debugPrint)
								System.out.println("Rendered");

							g2.setClip(null);

							// set up page hotspots
							if ((printAnnots != null)&& (printHotspots != null))
								printHotspots.setHotspots(printAnnots);


							/** draw any annotations */
							if (printHotspots != null)
								printHotspots.addHotspotsToDisplay(g2,
										userAnnotIcons, page);

							if (debugPrint)
								System.out.println("About to add annots/forms");

							// <start-forms>
							if (formsAvailable) {
								/**
								 * draw acroform data onto Panel
								 */
								if ((currentFormRenderer != null)
										&& (currentAcroFormData != null)) {

									/** make sure they exist */
									currentFormRenderer
											.createDisplayComponentsForPage(
													page, null,
													(float) scaling,
													0);

									// always use 0 for printing and extraction
									// on forms
									currentFormRenderer.renderFormsOntoG2(g2,
											page, (float) scaling, 0);

								}

								if ((showAnnotations)
										&& (currentAnnotRenderer != null)
										&& (printAnnotObject != null)) {

									/** make sure they exist */
									currentAnnotRenderer
											.createDisplayComponentsForPage(
													page, null,
													(float) scaling,
													0);

									// always use 0 for printing and extraction
									// on forms
									currentAnnotRenderer.renderFormsOntoG2(g2,
											page, (float) scaling, 0);
								}
							}
							// <end-forms>

							if (debugPrint)
								System.out.println("Added");

							// fudge to get border round just the page
							if ((useBorder) && (myBorder != null))
								myBorder.paintBorder(this, g2, crx, cry, crw,
										crh);// &&

							// turn on double buffering
							currentManager.setDoubleBufferingEnabled(true);

							if (!currentPrintDecoder.isPageSuccessful()) {
								operationSuccessful = false;
								pageErrorMessages = pageErrorMessages
										+ currentPrintDecoder
												.getPageFailureMessage();
							}

							if (debugPrint)
								System.out.println("Successful="
										+ operationSuccessful + "\n"
										+ pageErrorMessages);

						}
					}

				} catch (PdfFontException e) {
					operationSuccessful = false;
					pageErrorMessages = pageErrorMessages
							+ "Missing substitute fonts\n";

					if (debugPrint)
						System.out.println("Exception e=" + e);

				}

			}

		} catch (Error err) {
			operationSuccessful = false;
			pageErrorMessages = pageErrorMessages
					+ "Memory Error on printing\n";

			if (debugPrint)
				System.out.println("Error=" + err);

		}

		/** force printing to terminate on failure */
		if (!operationSuccessful) {
			i = Printable.NO_SUCH_PAGE;
		}

		if (debugPrint)
			System.out.println("return i=" + i);

		return i;

	}

	/**
	 * generate BufferedImage of a page in current file (Viewer/Ent only)
	 */
	public BufferedImage getPageAsImage(int pageIndex) throws PdfException {
		return getPageAsImage(pageIndex, false);
	}

	/**
	 * generate BufferedImage of a page in current file (Viewer/Ent only)
	 */
	public BufferedImage getPageAsTransparentImage(int pageIndex)
			throws PdfException {
		return getPageAsImage(pageIndex, true);
	}

	/**
	 * generate BufferedImage of a page in current file (Viewer/Ent only)
	 */
	private BufferedImage getPageAsImage(int pageIndex,
			boolean imageIsTransparent) throws PdfException {
		System.out.println("here");
		BufferedImage image = null;

		// make sure in range
		if ((pageIndex > pageCount) | (pageIndex < 1)) {
			LogWriter.writeLog("Page " + pageIndex + " not in range");

		} else {

			/**
			 * setup for decoding page
			 */
			PdfAnnots printAnnots = null;
			String printAnnotObject = null;

			/** get pdf object id for page to decode */
			String currentPageOffset = (String) pagesReferences
					.get(new Integer(pageIndex));

			if ((currentPageOffset != null)) {

				if (currentPdfFile == null)
					throw new PdfException(
							"File not open - did you call closePdfFile() inside a loop and not reopen");

				/** read page or next pages */
				Map values = currentPdfFile.readObject(currentPageOffset,
						false, null);

				/**
				 * read the Annotations for the page we have found lots of
				 * issues with annotations so trap errors
				 */
				try {
					// copied from decodepage to print the annotations
					printAnnotObject = currentPdfFile.getValue((String) values
							.get("Annots"));

					// <start-forms>
					if (formsAvailable) {
						if ((renderPage) && (printAnnotObject != null)
								&& (showAnnotations)) {

							printAnnots = new PdfAnnots(currentPdfFile,
									pageLookup);
							printAnnots.readAnnots(printAnnotObject);

							currentAnnotRenderer.init(printAnnots, insetW,
									insetH, pageData, currentPdfFile);

						}
					}
					// <end-forms>

				} catch (Exception e1) {
					LogWriter.writeLog("[PDF] " + e1 + " with annotation");
				}

				/** get information for the page */
				String value = (String) values.get("Contents");

				/** flush annotations */
				if (printHotspots != null)
					printHotspots.flushAnnotationsDisplayed();

				/**
				 * setup transformations and image
				 */
				AffineTransform imageScaling = setPageParametersForImage(
						scaling, pageIndex);

				int mediaW = pageData.getMediaBoxWidth(pageIndex);
				int mediaH = pageData.getMediaBoxHeight(pageIndex);
				int rotation = pageData.getRotation(pageIndex);

				int crw = pageData.getCropBoxWidth(pageIndex);
				int crh = pageData.getCropBoxHeight(pageIndex);
				int crx = pageData.getCropBoxX(pageIndex);
				int cry = pageData.getCropBoxY(pageIndex);

				boolean rotated = false;
				int w, h;
				if ((rotation == 90) | (rotation == 270)) {
					h = (int) (crw * scaling);
					w = (int) (crh * scaling);
					rotated = true;
				} else {
					w = (int) (crw * scaling);
					h = (int) (crh * scaling);
				}

				image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);

				Graphics graphics = image.getGraphics();

				Graphics2D g2 = (Graphics2D) graphics;
				if (!imageIsTransparent) {
					g2.setColor(Color.white);
					g2.fillRect(0, 0, w, h);
				}

				/**
				 * decode the contents or fill in white
				 */
				if (value != null) {

					ObjectStore localStore = new ObjectStore();
					PdfStreamDecoder currentImageDecoder = new PdfStreamDecoder();

					currentImageDecoder.setName(filename);
					currentImageDecoder.setStore(localStore);

					Map resValue = currentPdfFile.getSubDictionary(values
							.get("Resources"));

					if (imageIsTransparent) {

						DynamicVectorRenderer imageDisplay = new DynamicVectorRenderer(
								false, 5000, localStore);
						currentImageDecoder.init(true,true,renderMode,0,pageData,pageIndex,imageDisplay,currentPdfFile,globalRes,resValue);
						/**/
					} else {
						currentImageDecoder.init(true,true,renderMode,0,pageData,pageIndex,null,currentPdfFile,globalRes,resValue);
						/**/
					}

					/**
					 * pass in values as needed for patterns
					 */
					currentImageDecoder.getPdfStreamDecoder().setScalingValues(
							crx, crh + cry, scaling);

					g2.setRenderingHints(ColorSpaces.hints);
					g2.transform(imageScaling);

					if (rotated)
						g2.translate(-crx, cry);

					/** decode and print in 1 go */
					currentImageDecoder
							.setDirectRendering((Graphics2D) graphics);
					currentImageDecoder.decodePageContent(value, 0, 0, null);// mediaX,mediaY);

					g2.setClip(null);

					// set up page hotspots
					if ((printAnnots != null) && (printHotspots != null))
						printHotspots.setHotspots(printAnnots);

					/** draw any annotations */
					if (printHotspots != null)
						printHotspots.addHotspotsToDisplay(g2, userAnnotIcons,
								pageIndex);

					// <start-forms>
					if (formsAvailable) {
						/**
						 * draw acroform data onto Panel
						 */
						if ((currentFormRenderer != null)
								&& (currentAcroFormData != null)) {

							/** make sure they exist */
							currentFormRenderer.createDisplayComponentsForPage(
									pageIndex, this, scaling, 0);

							currentFormRenderer.renderFormsOntoG2(g2,
									pageIndex, scaling, 0);

						}

						/**
						 * draw acroform data onto Panel
						 */
						if ((showAnnotations) && (printAnnotObject != null)) {

							/** make sure they exist */
							currentAnnotRenderer
									.createDisplayComponentsForPage(pageIndex,
											this, scaling, 0);

							currentAnnotRenderer.renderFormsOntoG2(g2,
									pageIndex, scaling, 0);

						}
					}
					// <end-forms>


					localStore.flush();
				}
			}

			//if ((!imageIsTransparent) && (image != null))
				//image = ColorSpaceConvertor.convertToRGB(image);

		}

		return image;

	}
	
	/**
	 * generate BufferedImage of a page in current file (Viewer/Ent only)
	 */
	public void paintToGraphics2D(int pageIndex, boolean imageIsTransparent, Graphics2D g2) throws PdfException {
		
		// make sure in range
		if ((pageIndex > pageCount) | (pageIndex < 1)) {
			LogWriter.writeLog("Page " + pageIndex + " not in range");

		} else {

			/**
			 * setup for decoding page
			 */
			PdfAnnots printAnnots = null;
			String printAnnotObject = null;

			/** get pdf object id for page to decode */
			String currentPageOffset = (String) pagesReferences
					.get(new Integer(pageIndex));

			if ((currentPageOffset != null)) {

				if (currentPdfFile == null)
					throw new PdfException(
							"File not open - did you call closePdfFile() inside a loop and not reopen");

				/** read page or next pages */
				Map values = currentPdfFile.readObject(currentPageOffset,
						false, null);

				/**
				 * read the Annotations for the page we have found lots of
				 * issues with annotations so trap errors
				 */
				try {
					// copied from decodepage to print the annotations
					printAnnotObject = currentPdfFile.getValue((String) values
							.get("Annots"));

					// <start-forms>
					if (formsAvailable) {
						if ((renderPage) && (printAnnotObject != null)
								&& (showAnnotations)) {

							printAnnots = new PdfAnnots(currentPdfFile,
									pageLookup);
							printAnnots.readAnnots(printAnnotObject);

							currentAnnotRenderer.init(printAnnots, insetW,
									insetH, pageData, currentPdfFile);

						}
					}
					// <end-forms>

				} catch (Exception e1) {
					LogWriter.writeLog("[PDF] " + e1 + " with annotation");
				}

				/** get information for the page */
				String value = (String) values.get("Contents");

				/** flush annotations */
				if (printHotspots != null)
					printHotspots.flushAnnotationsDisplayed();

				/**
				 * setup transformations and image
				 */
				AffineTransform imageScaling = setPageParametersForImage(
						scaling, pageIndex);

				int mediaW = pageData.getMediaBoxWidth(pageIndex);
				int mediaH = pageData.getMediaBoxHeight(pageIndex);
				int rotation = pageData.getRotation(pageIndex);

				int crw = pageData.getCropBoxWidth(pageIndex);
				int crh = pageData.getCropBoxHeight(pageIndex);
				int crx = pageData.getCropBoxX(pageIndex);
				int cry = pageData.getCropBoxY(pageIndex);

				boolean rotated = false;
				int w, h;
				if ((rotation == 90) | (rotation == 270)) {
					h = (int) (crw * scaling);
					w = (int) (crh * scaling);
					rotated = true;
				} else {
					w = (int) (crw * scaling);
					h = (int) (crh * scaling);
				}

				

				

				
				if (!imageIsTransparent) {
					g2.setColor(Color.white);
					g2.fillRect(0, 0, w, h);
				}

				/**
				 * decode the contents or fill in white
				 */
				if (value != null) {

					ObjectStore localStore = new ObjectStore();
					PdfStreamDecoder currentImageDecoder = new PdfStreamDecoder();

					currentImageDecoder.setName(filename);
					currentImageDecoder.setStore(localStore);

					Map resValue = currentPdfFile.getSubDictionary(values
							.get("Resources"));

					if (imageIsTransparent) {

						DynamicVectorRenderer imageDisplay = new DynamicVectorRenderer(
								false, 5000, localStore);
						currentImageDecoder.init(true,true,renderMode,0,pageData,pageIndex,imageDisplay,currentPdfFile,globalRes,resValue);
						/**/
					} else {
						currentImageDecoder.init(true,true,renderMode,0,pageData,pageIndex,null,currentPdfFile,globalRes,resValue);
						/**/
					}

					/**
					 * pass in values as needed for patterns
					 */
					currentImageDecoder.getPdfStreamDecoder().setScalingValues(
							crx, crh + cry, scaling);

					g2.setRenderingHints(ColorSpaces.hints);
					g2.transform(imageScaling);

					if (rotated)
						g2.translate(-crx, cry);

					/** decode and print in 1 go */
					currentImageDecoder.setDirectRendering(g2);
					currentImageDecoder.decodePageContent(value, 0, 0, null);// mediaX,mediaY);

					g2.setClip(null);

					// set up page hotspots
					if ((printAnnots != null) && (printHotspots != null))
						printHotspots.setHotspots(printAnnots);

					/** draw any annotations */
					if (printHotspots != null)
						printHotspots.addHotspotsToDisplay(g2, userAnnotIcons,
								pageIndex);

					// <start-forms>
					if (formsAvailable) {
						/**
						 * draw acroform data onto Panel
						 */
						if ((currentFormRenderer != null)
								&& (currentAcroFormData != null)) {

							/** make sure they exist */
							currentFormRenderer.createDisplayComponentsForPage(
									pageIndex, this, scaling, 0);

							currentFormRenderer.renderFormsOntoG2(g2,
									pageIndex, scaling, 0);

						}

						/**
						 * draw acroform data onto Panel
						 */
						if ((showAnnotations) && (printAnnotObject != null)) {

							/** make sure they exist */
							currentAnnotRenderer
									.createDisplayComponentsForPage(pageIndex,
											this, scaling, 0);

							currentAnnotRenderer.renderFormsOntoG2(g2,
									pageIndex, scaling, 0);

						}
					}
					// <end-forms>


					localStore.flush();
				}
			}

			//if ((!imageIsTransparent) && (image != null))
				//image = ColorSpaceConvertor.convertToRGB(image);

		}

		//return image;

	}

	/**
	 * provide method for outside class to clear store of objects once written
	 * out to reclaim memory
	 * 
	 * @param reinit
	 *            lag to show if image data flushed as well
	 */
	final public void flushObjectValues(boolean reinit) {

		boolean inDemo=false; 
		/**/
		

		// <start-forms>
		if (currentAcroFormData != null)
			currentAcroFormData = new PdfFormData(inDemo);
		// <end-forms>


	}


	/**
	 * provide method for outside class to get Annots data object<br>
	 * Please note: Structure of PdfPageData is not guaranteed to remain
	 * constant<br>
	 * Please contact IDRsolutions for advice (pass in null object if using
	 * externally)
	 * 
	 * @return PdfAnnots object containing annotation data
	 */
	// <start-forms>
	final public PdfAnnots getPdfAnnotsData(AcroRenderer currentAnnotRenderer) {

		if (annotsData == null) {
			try {

				if (annotObject != null) {
					annotsData = new PdfAnnots(currentPdfFile, pageLookup);
					annotsData.readAnnots(annotObject);
				}

			} catch (Exception e) {
				LogWriter.writeLog("[PDF] " + e + " with annotation");
			}
		}

		// pass handle into renderer
		if (formsAvailable) {
			if (currentAnnotRenderer == null)
				currentAnnotRenderer = this.currentAnnotRenderer;

			if ((showAnnotations) && (currentAnnotRenderer != null))
				currentAnnotRenderer.init(annotsData, insetW, insetH, pageData,
						currentPdfFile);
		}

		return annotsData;
	}

	/**
	 * read the form data from the pdf file<br>
	 * 
	 * @param currentFormOffset -
	 *            object reference to form
	 */
	private void readAcroForm(Object currentFormOffset) {

		String value = "", formObject = "";

		// table to hold children
		Map kidData = new HashMap();

		// System.out.println(currentFormOffset);

		if (!useForms)
			return;

		LogWriter.writeLog("Form data being read");

		/** start the queue */
		Vector queue = new Vector();

		/**
		 * read form object metadata
		 */
		Map values;
		if (currentFormOffset instanceof String)
			values = currentPdfFile.readObject((String) currentFormOffset,
					false, null);
		else
			values = (Map) currentFormOffset;

		/** flag if XFA */
		isXFA = (values.get("XFA") != null);

		// System.out.println(currentFormOffset+">>"+values);


		/** read the fields */
		value = (String) values.get("Fields");
		if (value != null) {

			/** allow for values after fields */
			int p = value.indexOf("]");
			if (p != 0)
				value = value.substring(0, p + 1);

			/** strip the array braces */
			value = Strip.removeArrayDeleminators(value);

			boolean fieldsToProcess = true;

			boolean inDemo=false; 
			/**/

			/** put in queue */
			StringTokenizer initialValues = new StringTokenizer(value, "R");

			// allow for empty list

			// text fields
			Map fields = new HashMap();
			// setup a list of fields which are string values
			fields.put("T", "x");
			fields.put("TM", "x");
			fields.put("TU", "x");
			fields.put("CA", "x");
			fields.put("R", "x");
			fields.put("V", "x");
			fields.put("RC", "x");
			fields.put("DA", "x");
			fields.put("DV", "x");

			/** allow for empty queue */
			if (initialValues.hasMoreTokens()) {

				formObject = initialValues.nextToken().trim() + " R";
				// first value
				while (initialValues.hasMoreTokens())
					queue.addElement(initialValues.nextToken().trim() + " R");
			} else
				fieldsToProcess = false;

			StringTokenizer kidObjects = null;
			int kidCount;

			Map names = new HashMap();

			/** read each form object */
			while (fieldsToProcess) {

				/** read each form object */
				Map formData = currentPdfFile.readObject(formObject, false,
						fields);

				/** if its a kid with 1 element, add in data from parent */
				Map parentData = (Map) kidData.get(formObject);

				if (formData.containsKey("T")) {
					if (formData.containsKey("Kids")) {
						names.put(formObject, currentPdfFile
								.resolveToMapOrString("T", formData.get("T")));
					} else {
						Object parent = formData.get("Parent");
						if (parent == null) {
							formData.put("T", currentPdfFile
									.resolveToMapOrString("T", formData
											.get("T")));
						} else {
							formData.put("T", names.get(parent)
									+ "."
									+ currentPdfFile.resolveToMapOrString("T",
											formData.get("T")));
						}
					}
				}

				if (parentData != null) {
					Iterator i = parentData.keySet().iterator();

					while (i.hasNext()) {
						Object key = i.next();
						Object kidValue = parentData.get(key);

						if (!formData.containsKey(key))
							formData.put(key, kidValue);

					}
				}

				// System.out.println(formObject+" "+formData);
				String kids = (String) formData.get("Kids");
				if (kids != null) {

					String initialPageList = kids;
					if (initialPageList.startsWith("["))
						// handle any square brackets (ie arrays)
						initialPageList = initialPageList.substring(1,
								initialPageList.length() - 1).trim();

					// put kids in the queue
					kidObjects = new StringTokenizer(initialPageList, "R");
					/**
					 * allow for kids being used as a way to separate out
					 * distinct objects (ie tree structure) rather than kids as
					 * part of composite object (ie buttons in group)
					 */
					if (formData.get("FT") == null
							|| !(((String) formData.get("FT")).indexOf("Btn") != -1)) {
						kidCount = 1;
					} else {// must be a button
					// kidCount=1;
						kidCount = kidObjects.countTokens();

						String flag = (String) (formData.get("Ff"));
						if (flag != null) {
							int flagValue = Integer.parseInt(flag);

							// if((flagValue &
							// FormStream.RADIO)==FormStream.RADIO ||
							// !((flagValue &
							// FormStream.PUSHBUTTON)==FormStream.PUSHBUTTON)){
							if (((flagValue & FormStream.PUSHBUTTON) == FormStream.PUSHBUTTON)) {

								// kidCount=kidObjects.countTokens();
								kidCount = 1;
							}
						}
					}
				} else {
					kidCount = 0;
				}

				String type = (String) formData.get("Type");
				if ((kidCount > 1)
						| ((type != null) && (type.equals("/Annot")))) {
					/** setup forms object */
					if (currentAcroFormData == null)
						currentAcroFormData = new PdfFormData(inDemo);

					if (kidCount == 0)
						kidCount = 1;
					currentAcroFormData.incrementCount(kidCount);

					if (flattenDebug) {

						/**
						 * convert any indirect values into actual data and put
						 * in array
						 */
						Map cleanedFormData = new HashMap();
						currentPdfFile.flattenValuesInObject(true, true,
								formData, cleanedFormData, fields, pageLookup,
								formObject);

						formData = cleanedFormData;

					} else { // setup page
						// removed to fix abacus file as need original
						// PageNumber to divide
						formData.put("PageNumber", "1");

						// add page
						if (formData.containsKey("P")) {
							try {
								Object rawValue = formData.get("P");

								if (rawValue != null && pageLookup != null
										&& rawValue instanceof String) {
									int page = pageLookup
											.convertObjectToPageNumber((String) rawValue);
									formData.put("PageNumber", "" + page);
									// currentForm.remove("P");
								}
							} catch (Exception e) {
							}

						}

						// flatten any kids (not Issie or Patrick)
						if (kidCount > 1) {
							String kidrefs = (String) formData.get("Kids");

							// put kids in the queue
							kidObjects = new StringTokenizer(Strip
									.removeArrayDeleminators(kidrefs), "R");

							Map formObjects = new HashMap();

							while (kidObjects.hasMoreTokens()) {
								String next_value = kidObjects.nextToken()
										.trim()
										+ " R";

								Map kidValue = currentPdfFile.readObject(
										next_value, false, fields);

								kidValue.put("PageNumber", "1");

								// add page
								if (kidValue.containsKey("P")) {
									try {
										Object rawValue = kidValue.get("P");

										if (rawValue != null
												&& pageLookup != null
												&& rawValue instanceof String) {
											int page = pageLookup
													.convertObjectToPageNumber((String) rawValue);
											kidValue.put("PageNumber", ""
													+ page);

										}
									} catch (Exception e) {
									}
								}
								formObjects.put(next_value, kidValue);
							}

							formData.put("Kids", formObjects);
						}

					}

					formData.put("obj", formObject);

					/** store the element in our form object */
					try {
						currentAcroFormData.addFormElement(formData);
					} catch (Exception e) {
						e.printStackTrace();

					}

				} else if (kidCount == 1) { // separate out indirect (which we
											// flatten) from genuine groups

					// its an indirect kid [1 0 R] so flatten
					while (kidObjects.hasMoreTokens()) {
						String next_value = kidObjects.nextToken().trim()
								+ " R";
						queue.addElement(next_value);
						formData.remove("Kids");
						kidData.put(next_value, formData);
					}

				}

				// exit when all pages read
				if (queue.isEmpty() == true)
					break;

				// get next page from queue
				formObject = (String) queue.firstElement();

				// and remove from our queue to avoid infinite loop
				queue.removeElement(formObject);

			}
		}
	}

	private void convertXMLtoValues(StringBuffer xmlStream) {

		System.out.println("------XML stream-------");

		System.out.println(xmlStream);
		/**
		 * now parse the XML
		 */
		StringBufferInputStream str = new StringBufferInputStream(xmlStream
				.toString());

		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			Document doc = factory.newDocumentBuilder().parse(str);

			NodeList nodes = doc.getChildNodes();

			scanNodes(nodes, 0);

			str.close();

		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	// <end-forms>

	private void scanNodes(NodeList nodes, int level) {

		int count = nodes.getLength();

		StringBuffer space = new StringBuffer();
		for (int jj = 0; jj < level; jj++)
			space.append("   ");
		try {
			for (int i = 0; i < count; i++) {

				// get next node
				Node e = (Node) nodes.item(i);

				System.out.println(space + e.getNodeName() + "="
						+ e.getNodeValue() + " Type=" + e.getNodeType());

				// show attributes
				NamedNodeMap atts = e.getAttributes();
				if (atts != null) {
					int attCount = atts.getLength();
					for (int j = 0; j < attCount; j++) {
						Node cc = atts.item(j);
						System.out.println(space + "   " + cc.getNodeName()
								+ "=" + cc.getNodeValue());
					}
				}
				// carry on down tree
				if (e.hasChildNodes()) {
					// System.out.println(space+"children---------");
					scanNodes(e.getChildNodes(), level + 1);
					// System.out.println(space+"-----------------");
				}
			}
		} catch (Exception e) {
		}
	}

	/**
	 * @deprecated Please use setRenderMode(int mode)
	 */
	final public void setRenderMode(int mode, String[] newModes) {

		setRenderMode(mode);

	}

	/**
	 * set render mode to state what is displayed onscreen (ie
	 * RENDERTEXT,RENDERIMAGES) - only generally required if you do not wish to
	 * show all objects on screen (default is all). Add values together to
	 * combine settings.
	 * 
	 */
	final public void setRenderMode(int mode) {

		renderMode = mode;

	}


	/**
	 * method to return null or object giving access info fields and metadata.
	 */
	final public PdfFileInformation getFileInformationData() {

		if (currentPdfFile != null)
			return currentPdfFile.readPdfFileMetadata(XMLObject);
		else
			return null;

	}

	/**
	 * (Enterprise only) - gives fine tuning over what is extracted -
	 * 
	 * mode is a set of values which together show if text and images are
	 * rendered default is all can be set as a value for specific creator
	 * programs -
	 * 
	 * dpi is the image dpi - Images are assumed at 72dpi so a value of 72 will
	 * not cause any rescaling -
	 * 
	 * scaling is the page dpi as a factor of 72 (ie 1-72, 2=144)
	 * @throws PdfException 
	 */
	final public void setExtractionMode(int mode, int imageDpi, float scaling) throws PdfException {


		if (scaling < .5)
			scaling = .5f;

		this.scaling = scaling;


	}

	/**
	 * just extract annotations for a page - if you want to decode the page and
	 * extract the annotations use decodePage(int pageNumber) which does both.
	 * 
	 * Now returns PdfAnnots object
	 */
	final public PdfAnnots decodePageForAnnotations(int i) {

		/** set general values and update flag */
		String value;

		PdfAnnots annotsData = null;

		/** check in range */
		if (i > getPageCount()) {

			LogWriter.writeLog("Page out of bounds");

		} else {

			/** get pdf object id for page to decode */
			String currentPageOffset = (String) pagesReferences
					.get(new Integer(i));

			/**
			 * decode the file if not already decoded, there is a valid object
			 * id and it is unencrypted
			 */
			if ((currentPageOffset != null)) {

				// if(currentPdfFile==null)
				// throw new PdfException ("File not open - did you call
				// closePdfFile() inside a loop and not reopen");

				/** read page or next pages */
				Map values = currentPdfFile.readObject(currentPageOffset,
						false, null);

				/**
				 * read the annotationations for the page we have found lots of
				 * issues with annotations so trap errors
				 */
				try {
					value = currentPdfFile.getValue((String) values
							.get("Annots"));

					if (value != null) {
						annotsData = new PdfAnnots(currentPdfFile, pageLookup);
						annotsData.readAnnots(value);
					}

				} catch (Exception e) {
					LogWriter.writeLog("[PDF] " + e + " with annotation");
				}
			}
		}

		return annotsData;
	}

	/**
	 * get pdf as Image of any page scaling is size (100 = full size)
	 */
	final public BufferedImage getPageAsThumbnail(int pageNumber, int h) {

		BufferedImage image = null;
		int mediaX = 0, mediaY = 0, mediaW = 0, mediaH = 0;

		/** the actual display object */
		ObjectStore localStore = new ObjectStore();
		DynamicVectorRenderer imageDisplay = new DynamicVectorRenderer(true,
				1000, localStore); //
		// simageDisplay.setDirectRendering((Graphics2D) graphics);

		try {

			/** check in range */
			if (pageNumber > getPageCount()) {

				LogWriter.writeLog("Page " + pageNumber + " out of bounds");

			} else {

				/** resolve page size */
				mediaX = pageData.getMediaBoxX(pageNumber);
				mediaY = pageData.getMediaBoxY(pageNumber);
				mediaW = pageData.getMediaBoxWidth(pageNumber);
				mediaH = pageData.getMediaBoxHeight(pageNumber);

				/** get pdf object id for page to decode */
				String currentPageOffset = (String) pagesReferences
						.get(new Integer(pageNumber));

				/**
				 * decode the file if not already decoded, there is a valid
				 * object id and it is unencrypted
				 */
				if ((currentPageOffset != null)) {

					/** read page or next pages */
					Map values = currentPdfFile.readObject(currentPageOffset,
							false, null);

					/** get information for the page */
					String value = (String) values.get("Contents");

					if (value != null) {

						PdfStreamDecoder imageDecoder = new PdfStreamDecoder();
						imageDecoder.setName(filename);
						imageDecoder.setStore(localStore);
						Map resValue = currentPdfFile.getSubDictionary(values
								.get("Resources"));

						imageDecoder.init(true, true, renderMode, 0, pageData,
								pageNumber, imageDisplay, currentPdfFile,
								globalRes, resValue);

						int rotation = pageData.getRotation(pageNumber);
						imageDisplay.init(mediaW, mediaH, rotation);
						imageDecoder.decodePageContent(value, mediaX, mediaY,
								null);

						imageDecoder = null;

					}
				}
			}
		} catch (Exception e) {
			e.printStackTrace();

		}

		localStore.flush();

		/**
		 * workout scaling and get image
		 */
		image = getImageFromRenderer(h, imageDisplay, pageNumber);

		return image;

	}

	/**
	 * set status bar to use when decoding a page - StatusBar provides a GUI
	 * object to display progress and messages.
	 * 
	 */
	public void setStatusBarObject(StatusBar statusBar) {
		this.statusBar = statusBar;
	}

	/**
	 * ask JPedal if stopDecoding() request completed
	 */
	private boolean isDecoding() {

		boolean decodeStatus = true;

		if ((!isDecoding) && ((current == null) || (current.exitedDecoding())))
			decodeStatus = false;
		// System.out.println(isDecoding+" "+current+" "+current);
		return decodeStatus;
	}

	/**
	 * ask JPedal to stop printing a page
	 */
	final public void stopPrinting() {
		stopPrinting = true;
	}

	/**
	 * ask JPedal to stop decoding a page
	 */
	private void stopDecoding() {

		stopDecoding = true;

		if (current != null)
			current.terminateDecoding();

		// wait to die
		while (isDecoding()) {
			// System.out.println("Waiting to die");
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				// should never be called
				e.printStackTrace();
			}
		}

		stopDecoding = false;

		currentDisplay.flush();
		screenNeedsRedrawing = true;

		// <start-forms>
		if (renderPage && formsAvailable) {
			if (currentFormRenderer != null)
				currentFormRenderer.removeDisplayComponentsFromScreen(this);

			if ((currentAnnotRenderer != null) && (showAnnotations))
				currentAnnotRenderer.removeDisplayComponentsFromScreen(this);

			invalidate();
		}
		// <end-forms>

	}

	/**
	 * decode a page, - <b>page</b> must be between 1 and
	 * <b>PdfDecoder.getPageCount()</b> - Will kill off if already running
	 */
	final public void decodePage(int page) throws Exception {

		
		decodeStatus = "";

		/**
		 * shutdown if already decoding previous page
		 */
		stopDecoding();

		if (isDecoding) {
			LogWriter
					.writeLog("[PDF]WARNING - this file is being decoded already");
		} else {


			isDecoding = true; // can be switched off to instrcut method to
								// exit asap

			cursorBoxOnScreen = null;

			// <start-forms>
			if ((renderPage) && (formsAvailable)) {
				if (currentFormRenderer != null)
					currentFormRenderer.removeDisplayComponentsFromScreen(this);

				if ((currentAnnotRenderer != null) && (showAnnotations))
					currentAnnotRenderer
							.removeDisplayComponentsFromScreen(this);

				invalidate();
			}
			// <end-forms>

			/** flush renderer */
			currentDisplay.flush();
			screenNeedsRedrawing = true;

			// reset over-ride which may have been enabled
			overRideAcceleration = false;

			/** set general values and update flag */
			String value;

			/** check in range */
			if (page > getPageCount() || page < 1) {

				LogWriter.writeLog("Page out of bounds");

			} else if (!stopDecoding) {

				// <start-13>
				/**
				 * title changes to give user something to see under timer
				 * control
				 */
				Timer t = null;
				if (statusBar != null) {
					ActionListener listener = new ProgressListener();
					t = new Timer(500, listener);
					t.start(); // start it
				}
				// <end-13>

				this.pageNumber = page;

				/** get pdf object id for page to decode */
				String currentPageOffset = (String) pagesReferences
						.get(new Integer(page));

				/**
				 * decode the file if not already decoded, there is a valid
				 * object id and it is unencrypted
				 */
				if ((currentPageOffset != null) && (!stopDecoding)) {

					if (currentPdfFile == null)
						throw new PdfException(
								"File not open - did you call closePdfFile() inside a loop and not reopen");

					/** read page or next pages */
					Map values = currentPdfFile.readObject(currentPageOffset,
							false, null);

					/**
					 * read the annotations reference for the page we have found
					 * lots of issues with annotations so trap errors
					 */
					annotObject = currentPdfFile.getValue((String) values
							.get("Annots"));
					annotsData = null;
					// <start-forms>
					if ((renderPage) && (!stopDecoding))
						annotsData = getPdfAnnotsData(currentAnnotRenderer);
					// <end-forms>

					/** get information for the page */
					value = (String) values.get("Contents");

					/** flush annotations */
					if ((displayHotspots != null) && (!stopDecoding))
						displayHotspots.flushAnnotationsDisplayed();

					// if (!value.equals("null"))
					if ((value != null) && (!stopDecoding)) {

						current = new PdfStreamDecoder(useHiResImageForDisplay);
						current.setName(filename);
						current.setStore(objectStoreRef);
						if (includeImages)
							current.includeImages();

						/** pass in statusBar */
						current.setStatusBar(statusBar);

						/** set hires mode or not for display */
						currentDisplay
								.setHiResImageForDisplayMode(useHiResImageForDisplay);

						Map resValue = currentPdfFile.getSubDictionary(values
								.get("Resources"));

						// if not present, look for it back up tree
						if ((resValue == null) && (!stopDecoding)) {
							String parent = (String) values.get("Parent");
							while ((parent != null) && (resValue == null)) {
								Map parentValues = currentPdfFile.readObject(
										parent, false, null);

								Object res = parentValues.get("Resources");
								if (res == null) {
									parent = (String) parentValues
											.get("Parent");
								} else if (res instanceof String)
									resValue = currentPdfFile
											.getSubDictionary(res);
								else
									resValue = (Map) res;
							}
						}

						if (!stopDecoding) {
							current.init(true,true,7,0,pageData,page,currentDisplay,currentPdfFile,globalRes,resValue);
							/**/
						}

						/** pass in visual multithreaded status bar */
						if (!stopDecoding)
							current.setStatusBar(statusBar);

						int mediaW = pageData.getMediaBoxWidth(pageNumber);
						int mediaH = pageData.getMediaBoxHeight(pageNumber);
						int mediaX = pageData.getMediaBoxX(pageNumber);
						int mediaY = pageData.getMediaBoxY(pageNumber);
						int rotation = pageData.getRotation(pageNumber);
						currentDisplay.init(mediaW, mediaH, rotation);
						/** toke out -min's%% */

						if (g2 != null)
							current.setDirectRendering(g2);

						try {
							if (!stopDecoding)
								current.decodePageContent(value, 0, 0, null);// mediaX,mediaY);/**removed
																				// min_x,min_y%%*/
						} catch (Error err) {
							decodeStatus = decodeStatus
									+ "Error in decoding page "
									+ err.toString();
						}

						if (!stopDecoding) {
							hasEmbeddedFonts = current.hasEmbeddedFonts();

							fontsInFile = PdfStreamDecoder.getFontsInFile();

						}

						imagesProcessedFully = current.hasAllImages();
						
						current = null;

					}
				}

				/** turn off status bar update */
				// <start-13>
				if (t != null) {

					t.stop();
					statusBar.setProgress(100);

				}
				// <end-13>

				// <start-forms>
				/**
				 * draw acroform data onto Panel
				 */
				if ((formsAvailable) && (renderPage) && (!stopDecoding)) {

					if ((currentFormRenderer != null)
							&& (currentAcroFormData != null) && (!stopDecoding)) {
						currentFormRenderer.createDisplayComponentsForPage(
								page, this, scaling, displayRotation);
					}

					if ((showAnnotations) && (currentAnnotRenderer != null)
							&& (annotsData != null) && (!stopDecoding)) {
						currentAnnotRenderer.createDisplayComponentsForPage(
								page, this, scaling, displayRotation);
					}

					this.validate();
				}
				// <end-forms>

				// set up page hotspots
				if ((annotsData != null) && (displayHotspots != null)
						&& (!stopDecoding))
					displayHotspots.setHotspots(annotsData);
			}

			screenNeedsRedrawing = true;
			current = null;
			isDecoding = false;

		}
	}

	/**
	 * uses hires images to create a higher quality display - downside is it is
	 * slower and uses more memory (default is false).- Does nothing in OS
	 * version
	 * 
	 * @param value
	 * @throws Exception
	 */
	public void useHiResScreenDisplay(boolean value) {
	}


	/**
	 * get page count of current PDF file
	 */
	final public int getPageCount() {
		return pageCount;
	}

	/**
	 * return true if the current pdf file is encrypted <br>
	 * check <b>isFileViewable()</b>,<br>
	 * <br>
	 * if file is encrypted and not viewable - a user specified password is
	 * needed.
	 */
	final public boolean isEncrypted() {
		if (currentPdfFile != null)
			return currentPdfFile.isEncrypted();
		else
			return false;
	}

	/** show if encryption password has been supplied */
	final public boolean isPasswordSupplied() {
		if (currentPdfFile != null)
			return currentPdfFile.isPasswordSupplied();
		else
			return false;
	}

	/**
	 * show if encrypted file can be viewed,<br>
	 * if false a password needs entering
	 */
	public boolean isFileViewable() {
		if (currentPdfFile != null)
			return currentPdfFile.isFileViewable();
		else
			return false;
	}

	/** show if content can be extracted */
	public boolean isExtractionAllowed() {
		if (currentPdfFile != null)
			return currentPdfFile.isExtractionAllowed();
		else
			return false;
	}

	/**
	 * used to retest access and see if entered password is valid,<br>
	 * If so file info read and isFileViewable will return true
	 */
	final public void verifyAccess() {
		if (currentPdfFile != null) {
			try {
				openPdfFile();
			} catch (Exception e) {
				LogWriter.writeLog("Exception " + e + " opening file");
			}
		}

	}

	/**
	 * set the font used for default from Java fonts on system - Java fonts are
	 * case sensitive, but JPedal resolves this internally, so you could use
	 * Webdings, webdings or webDings for Java font Webdings - checks if it is a
	 * valid Java font (otherwise it will default to Lucida anyway)
	 */
	public final void setDefaultDisplayFont(String fontName)
			throws PdfFontException {

		boolean isFontInstalled = false;

		// get list of fonts and see if installed
		String[] fontList = GraphicsEnvironment.getLocalGraphicsEnvironment()
				.getAvailableFontFamilyNames();

		int count = fontList.length;

		for (int i = 0; i < count; i++) {
			if (fontList[i].toLowerCase().equals(fontName.toLowerCase())) {
				isFontInstalled = true;
				defaultFont = fontList[i];
				i = count;
			}
		}

		if (isFontInstalled == false)
			throw new PdfFontException("Font " + fontName
					+ " is not available.");

	}

	/**
	 * set a password for encryption - software will resolve if user or owner
	 * password
	 */
	final public void setEncryptionPassword(String password) {
		currentPdfFile.setEncryptionPassword(password);
	}

	/**
	 * routine to open a byte stream cntaining the PDF file and extract key info
	 * from pdf file so we can decode any pages. Does not actually decode the
	 * pages themselves.
	 */
	final public void openPdfArray(byte[] data) throws PdfException {

		// System.err.println("1");

		LogWriter.writeMethod("{openPdfArray}", 0);

		try {

			// System.err.println("2");
			currentPdfFile = new PdfObjectReader();

			// System.err.println("3");
			/** get reader object to open the file */
			currentPdfFile.openPdfFile(data);

			// System.err.println("4");
			openPdfFile();

			// System.err.println("5");
			/** store file name for use elsewhere as part of ref key without .pdf */
			objectStoreRef.storeFileName("r" + System.currentTimeMillis());

			// System.err.println("6");
		} catch (Exception e) {
			throw new PdfException("[PDF] OpenPdfArray generated exception "
					+ e.getMessage());
		}
	}

	/**
	 * routine to open PDF file and extract key info from pdf file so we can
	 * decode any pages. Does not actually decode the pages themselves. Also
	 * reads the form data. You must explicitly close any open files with
	 * closePdfFile() to Java will not release all the memory
	 */
	final public void openPdfFile(final String filename) throws PdfException {


		LogWriter.writeMethod("{openPdfFile " + filename + "}", 0);

		this.filename = filename;

		/** store file name for use elsewhere as part of ref key without .pdf */
		objectStoreRef.storeFileName(filename);

		/**
		 * possible caching of code File testFile=new File(filename);
		 * 
		 * int size=(int)testFile.length(); if(size<300*1024){ byte[]
		 * fileData=new byte[size]; // read the object try {
		 * 
		 * FileInputStream fis=new FileInputStream(testFile);
		 * 
		 * //get binary data fis.read( fileData ); } catch( Exception e ) {
		 * LogWriter.writeLog( "Exception " + e + " reading from file" ); }
		 * 
		 * openPdfFile(fileData); }else
		 */

		currentPdfFile = new PdfObjectReader();

		/** get reader object to open the file */
		currentPdfFile.openPdfFile(filename);

		openPdfFile();

	}

	/**
	 * routine to open PDF file via URL and extract key info from pdf file so we
	 * can decode any pages - Does not actually decode the pages themselves -
	 * Also reads the form data - Based on an idea by Peter Jacobsen
	 * 
	 * You must explicitly close any open files with closePdfFile() to Java will
	 * not release all the memory
	 */
	final public void openPdfFileFromURL(String pdfUrl) throws PdfException {

		LogWriter.writeMethod("{openPdfFileFromURL " + pdfUrl + "}", 0);

		URL url = null;
		byte[] pdfByteArray = null;
		InputStream is = null;
		ByteArrayOutputStream os = null;

		try {
			url = new URL(pdfUrl);
			is = url.openStream();
			os = new ByteArrayOutputStream();

			// Download buffer
			byte[] buffer = new byte[4096];

			// Download the PDF document
			int read = 0;
			while ((read = is.read(buffer)) != -1) {
				os.write(buffer, 0, read);
			}
			// Copy output stream to byte array
			pdfByteArray = os.toByteArray();

			// Close streams
			is.close();
			os.close();

		} catch (IOException e) {
			LogWriter.writeLog("[PDF] Exception " + e + " opening URL "
					+ pdfUrl);
		}

		currentPdfFile = new PdfObjectReader();

		/** get reader object to open the file */
		currentPdfFile.openPdfFile(pdfByteArray);

		openPdfFile();

		/** store file name for use elsewhere as part of ref key without .pdf */
		objectStoreRef.storeFileName("<raw data>");
	}

	/**
	 * common code to all open routines
	 */
	private void openPdfFile() throws PdfException {

		LogWriter.writeMethod("{openPdfFile}", 0);

		// enusre no previous file still being decoded
		stopDecoding();

		try {
			isDecoding = true;


			//see if pejs.jar present for non OS version and scream loudly if it is 
			 /**/

			// set cache size to use
			currentPdfFile.setCacheSize(minimumCacheSize);

			// reset printing
			lastPrintedPage = -1;
			this.currentPrintDecoder = null;

			// rest page views
			lastPageChecked = -1;
			lastState = -1;

			// <start-forms>
			if (formsAvailable) {
				if (currentFormRenderer != null) {
					currentFormRenderer.removeDisplayComponentsFromScreen(this);
				}

				if ((showAnnotations) && (currentAnnotRenderer != null)) {
					currentAnnotRenderer
							.removeDisplayComponentsFromScreen(this);
				}
			}

			invalidate();
			// <end-forms>

			if (stopDecoding)
				return;

			// reset page data - needed to flush crop settings
			pageData = new PdfPageData();

			// read and log the version number of pdf used
			pdfVersion = currentPdfFile.getType();

			LogWriter.writeLog("Pdf version : " + pdfVersion);

			if (pdfVersion.indexOf("1.5") != -1)
				LogWriter
						.writeLog("Please note Pdf version 1.5  some features not fully supported ");
			else if (pdfVersion.indexOf("1.6") != -1)
				LogWriter
						.writeLog("Please note Pdf version 1.6  new features not fully supported ");

			LogWriter.writeMethod("{about to read ref table}", 0);

			// read reference table so we can find all objects and say if
			// encrypted
			String root_id = currentPdfFile.readReferenceTable();

			if (stopDecoding)
				return;

			// read the catalog
			LogWriter.writeMethod("{about to read catalog}", 0);

			Map values = currentPdfFile.readObject(root_id, false, null);

			if (stopDecoding)
				return;

			// open if not encrypted or has password
			if ((!this.isEncrypted()) | (this.isPasswordSupplied())) {

				// read any info and assign to global value
				XMLObject = (String) values.get("Metadata");

				// get pointer to pages and read the read page info
				String value = (String) values.get("Pages");
				LogWriter.writeMethod("{about to read pages}", 0);

				if (stopDecoding)
					return;

				if (value != null) {
					LogWriter.writeLog("Pages being read " + value);
					pageNumber = 1; // reset page number for metadata

					// reset lookup table
					pageLookup = new PageLookup();

					readAllPageReferences(value);

					pageCount = pageNumber - 1; // save page count
					pageNumber = 0; // reset page number for metadata;

					if (this.getPageCount() == 0)
						LogWriter.writeLog("No pages found");
				}

				if (stopDecoding)
					return;

				// read any names
				Object names = null;
				try {
					names = values.get("Names");
					if (names != null)
						currentPdfFile.readNames(names);

				} catch (Exception e) {
					LogWriter.writeLog("Exception reading Names object "
							+ names + " " + objectStoreRef.fullFileName);
					System.out.println(values);
					e.printStackTrace();
				}

				if (stopDecoding)
					return;

				// read any info and assign to global value
				outlineObject = values.get("Outlines");
				outlineData = null;
				if (outlineObject != null)
					hasOutline = true;
				else
					hasOutline = false;

				isXFA = false;

				if (stopDecoding)
					return;

				// Read any form data
				Object rawValue = values.get("AcroForm");
				if (rawValue != null) {
					// <start-forms>
					/**
					 * reinitialize formRenderer for a file with AcroForms
					 */
					// if(currentFormRenderer==null)
					// currentFormRenderer=new DefaultAcroRenderer();
					// LogWriter.writeLog("Acro Form being read ");
					readAcroForm(rawValue);

					// <end-forms>
					// LogWriter.writeLog("Data read");
					isForm = true;
				} else {
					isForm = false;

					// <start-forms>
					currentAcroFormData = null;

					// <end-forms>
				}

				// <start-forms>
				// pass handle into renderer
				if (formsAvailable) {
					if ((currentFormRenderer != null)) {
						currentFormRenderer.openFile(pageCount);
						currentFormRenderer.init(currentAcroFormData, insetW,
								insetH, pageData, currentPdfFile);
					}

					// flush Annots structures - required as Annots exist on
					// page level, Forms on Doc level
					if ((showAnnotations) && (currentAnnotRenderer != null))
						currentAnnotRenderer.openFile(pageCount);
				}
				// <end-forms>

			}

			// reset so if continuous view mode set it will be recalculated for
			// page
			viewCalculated = false;

			isDecoding = false;

		} catch (PdfException e) {
			isDecoding = false;
			throw new PdfException(e.getMessage() + " opening file");
		}

	}


	/**
	 * read the data from pages lists and pages so we can open each page.
	 * 
	 * @param currentPageOffset -
	 *            object reference to first trailer
	 */
	private void readAllPageReferences(String currentPageOffset) {

		LogWriter.writeMethod("{readAllPageReferences " + currentPageOffset
				+ "}", 0);

		String value = "", type = "";

		// LogWriter.writeLog("Page metadata being read for
		// "+currentPageOffset);

		Map values = currentPdfFile.readObject(currentPageOffset, false, null);

		// get if kid, pages, page
		type = (String) values.get("Type");
		if (type == null)
			type = "/Pages";

		/**
		 * handle common values which can occur at page level or higher
		 */

		/** page rotation */
		value = currentPdfFile.getValue((String) values.get("Rotate"));
		if (value == null)
			value = "0";
		pageData.setPageRotation(value, pageNumber);

		/**
		 * handle media and crop box, defaulting to higher value if needed (ie
		 * Page uses Pages and setting crop box
		 */
		String mediaValue = currentPdfFile.getValue((String) values
				.get("MediaBox"));
		if (mediaValue != null)
			pageData.setMediaBox(mediaValue);
		else {
			String testType = type;
			if (type.equals("Page")) {
				mediaValue = (String) globalMediaValues.get("Pages");
				if (mediaValue == null)
					testType = "Kids";
			}
			if ((mediaValue == null) && (testType.equals("Kids"))) {

				mediaValue = (String) globalMediaValues.get("Kids");
				if (mediaValue == null)
					testType = "Catalog";
			}
			if ((mediaValue == null) && (testType.equals("Catalog"))) {

				mediaValue = (String) globalMediaValues.get("Catalog");
			}
			if (mediaValue == null)
				mediaValue = "0 0 800 800";
		}

		value = currentPdfFile.getValue((String) values.get("CropBox"));
		if (value != null)
			pageData.setCropBox(value);
		if (!type.equals("Page") && (mediaValue != null)) {
			globalMediaValues.put(type, mediaValue);
		}

		/** process page ro read next level down */
		if (type.indexOf("/Pages") != -1) {

			globalRes = currentPdfFile
					.getSubDictionary(values.get("Resources"));

			value = Strip.removeArrayDeleminators(currentPdfFile
					.getValue((String) values.get("Kids"))); // get initial
																// pages

			if (value.length() > 0) {
				/** allow for empty value and put next pages in the queue */
				StringTokenizer initialValues = new StringTokenizer(value, "R");
				while (initialValues.hasMoreTokens())
					readAllPageReferences(initialValues.nextToken().trim()
							+ " R");
				// queue.addElement(initialValues.nextToken().trim() + " R");
			}
		} else if (type.indexOf("/Page") != -1) {
			// store ref for later
			pagesReferences.put(new Integer(pageNumber), currentPageOffset);
			pageLookup.put(currentPageOffset, pageNumber);

			pageData.checkSizeSet(pageNumber); // make sure we have min values
												// for page size

			pageNumber++;
		}
	}


	/**
	 * allow user to set own icons for annotation hotspots to display in
	 * renderer - pass user selection of hotspots as an array of format
	 * Image[number][page] where number is Annot number on page and page is
	 * current page -1 (ie 0 is page 1).
	 */
	public void addUserIconsForAnnotations(int page, String type, Image[] icons) {

		if (userAnnotIcons == null)
			userAnnotIcons = new Hashtable();

		userAnnotIcons.put((page) + "-" + type, icons);

		if (displayHotspots == null) {
			displayHotspots = new Hotspots();
			printHotspots = new Hotspots();
		}

		/** ensure type logged */
		displayHotspots.checkType(type);
		printHotspots.checkType(type);
	}

	/**
	 * initialise display hotspots and save global values
	 */
	public void createPageHostspots(String[] annotationTypes, String string) {
		displayHotspots = new Hotspots(annotationTypes, string);
		printHotspots = new Hotspots(annotationTypes, string);

	}

	/**
	 * show the imageable area in printout for debugging purposes
	 */
	public void showImageableArea() {

		showImageable = true;

	}

	/**
	 * part of pageable interface
	 *
	 * @see java.awt.print.Pageable#getNumberOfPages()
	 */
	public int getNumberOfPages() {

		int count=1;

		if (end != -1){
			count= end - start + 1;
			if(count<0) //allow for reverse order
				count=2-count;
		}

		return count;
	}

	/**
	 * part of pageable interface
	 *
	 * @see java.awt.print.Pageable#getPageFormat(int)
	 */
	public PageFormat getPageFormat(int p) throws IndexOutOfBoundsException {

		Object returnValue = null;

		int actualPage;

		if (end == -1)
			actualPage=p+1;
		else if(end>start)
			actualPage=start+p;
		else
			actualPage=start-p;

		returnValue = pageFormats.get(new Integer(actualPage));

		if (this.debugPrint)
			System.out.println(returnValue + " Get page format for page p=" + p
					+ " start=" + start + " pf=" + pageFormats + " "
					+ pageFormats.keySet());

		if (returnValue == null) {
			returnValue = pageFormats.get("standard");
			if (this.debugPrint)
				System.out.println(returnValue + " returned for standard");
		}

		PageFormat pf = new PageFormat();

		if (returnValue != null)
			pf = (PageFormat) returnValue;


        if(1==2){//PdfPanel.AUTO_ROTATE_AND_CENTER){

            pf.setOrientation(PageFormat.PORTRAIT);
        }else{
            int crw = pageData.getCropBoxWidth(actualPage);
            int crh = pageData.getCropBoxHeight(actualPage);


            //Set PageOrientation to best use page layout
            int orientation = crw < crh ? PageFormat.PORTRAIT: PageFormat.LANDSCAPE;
            pf.setOrientation(orientation);

        }

        return pf;
    }

	/**
	 * part of pageable interface
	 *
	 * @see java.awt.print.Pageable#getPrintable(int)
	 */
	public Printable getPrintable(int page) throws IndexOutOfBoundsException {

		return this;
	}

	/**
	 * set pageformat for a specific page - if no pageFormat is set a default
	 * will be used. Recommended to use setPageFormat(PageFormat pf)
	 */
	public void setPageFormat(int p, PageFormat pf) {

		if (this.debugPrint)
			System.out.println("Set page format for page " + p);

		pageFormats.put(new Integer(p), pf);

	}

	/**
	 * set pageformat for a specific page - if no pageFormat is set a default
	 * will be used.
	 */
	public void setPageFormat(PageFormat pf) {

		if (this.debugPrint)
			System.out.println("Set page format Standard for page");

		pageFormats.put("standard", pf);

	}


	/**
	 * remove all displayed objects for JPanel display (wipes current page)
	 */
	public void clearScreen() {
		currentDisplay.flush();
		screenNeedsRedrawing = true;
	}

	/**
	 * allows user to cache large objects to disk to avoid memory issues,
	 * setting minimum size in bytes (of uncompressed stream) above which object
	 * will be stored on disk if possible (default is -1 bytes which is all
	 * objects stored in memory) - Must be set before file opened.
	 */
	public void setStreamCacheSize(int size) {
		this.minimumCacheSize = size;
	}

	/**
	 * used to display non-PDF files
	 */
	public void addImage(BufferedImage img) {
		currentDisplay.drawImage(img);

	}

	/**
	 * shows if embedded fonts present on page just decoded
	 */
	public boolean hasEmbeddedFonts() {
		return hasEmbeddedFonts;
	}

	/** convert form ref into actual object */
	public Map resolveFormReference(String ref) {

		// text fields
		Map fields = new HashMap();

		// setup a list of fields which are string values
		fields.put("T", "x");
		fields.put("TM", "x");
		fields.put("TU", "x");
		fields.put("CA", "x");
		fields.put("R", "x");
		fields.put("V", "x");
		fields.put("RC", "x");
		fields.put("DA", "x");
		fields.put("DV", "x");

		return currentPdfFile.readObject(ref, false, fields);

	}


	/**
	 * Returns list of the fonts used on the current page decoded
	 */
	public String getFontsInFile() {
		if (fontsInFile == null)
			return "No fonts defined";
		else
			return fontsInFile;
	}

	/**
	 * include image data in PdfData - <b>not part of API, please do not use</b>
	 */
	public void includeImagesInStream() {
		includeImages = true;
	}

	/**
	 * return lines on page after decodePage run - <b>not part of API, please do
	 * not use</b>
	 */
	public PageLines getPageLines() {
		return this.pageLines;
	}

	/**
	 *
	 * if <b>true</b> uses the original jpeg routines provided by sun, else
	 * uses the imageIO routine in java 14 which is default<br>
	 * only required for PDFs where bug in some versions of ImageIO fails to
	 * render JPEGs correctly
	 */
	public void setEnableLegacyJPEGConversion(boolean newjPEGConversion) {

		use13jPEGConversion = newjPEGConversion;
	}

	/**
	 * allow user to override default setting(true) for transparency on
	 * extracted thumbnails (Not generally needed)
	 *
	 * @deprecated from 2.50
	 */
	public void setThumbnaiImageTransparency(boolean b) {

		// imageIsTransparent=b;
	}

	/**
	 * <b>true</b>, the scaling used on the pdf viewer is used for the scaling
	 * on the printed page<br>
	 *
	 * <b>false</b>, printing will use 100% scaling for printing<br>
	 * all printing will be scaled to fit page if it is oversized
	 *
	 * @deprecated from 2.71 see setPrintPageScalingMode(int pageScalingMode)
	 */
	public void enableScaledPrinting(boolean scalePrinting) {

		this.usePageScaling = scalePrinting;
	}

	/**
	 * return scaling factor applied to page when printing
	 *
	 * @deprecated from 2.71 see setPrintPageScalingMode(int pageScalingMode)
	 */
	public float getScaleForPrinting() {
		if (usePageScaling && scaling < 1)
			return scaling;
		else
			return 1f;
	}

	/**
	 * returns default bookmark to select for each page - not part of API and
	 * not live
	 *
	 * public Map getPointsForPage(){ return outlineData.getPointsForPage();
	 *  }
	 */

	/**
	 * return default bookmakrs for page - not part of API
	 *
	 * public String[] getOutlineDefaultReferences() { return
	 * outlineData.getDefaultBookmarksForPage(); }
	 */

	/**
	 * returns default bookmark to select for each page - not part of API and
	 * not live
	 *
	 * public Map getPointsForPage(){ return outlineData.getPointsForPage();
	 *  }
	 */

	/** used to update statusBar object if exists */
	class ProgressListener implements ActionListener {

		public void actionPerformed(ActionEvent evt) {

			statusBar.setProgress((int) (statusBar.percentageDone));
		}

	}

	// <start-forms>

	/**
	 * replace default FormFactory (which generates GUI widgets for display with
	 * your own
	 */
	public void setCurrentFormFactory(FormFactory newFormFactory) {
		if (formsAvailable)
			currentFormRenderer.setFormFactory(newFormFactory);
	}

	/**
	 * (Forms versions only) - It is strongly recommended you now override the
	 * DefaultFormFactory, using the setCurrentFormFactory() method,
	 * implementing your own form factory class - Allow user to replace class
	 * drawing Acroforms with own version (must implement AcroRenderer
	 * interface)<br> - Use DefaultAcroRenderer as a template and to override
	 *
	 * @deprecated from 2.50
	 */
	public void setCurrentFormRenderer(AcroRenderer newFormRenderer) {
		this.currentFormRenderer = newFormRenderer;
	}

	/**
	 * Allow user to access Forms object - returns null not available
	 */
	public AcroRenderer getCurrentFormRenderer() {
		if (!this.formsAvailable)
			return null;
		else
			return currentFormRenderer;
	}

	// <end-forms>

	/**
	 * shows if page reported any errors while printing or being decoded. Log
	 * can be found with getPageFailureMessage()
	 *
	 * @return Returns the printingSuccessful.
	 */
	public boolean isPageSuccessful() {
		return operationSuccessful;
	}

	/**
	 * return any errors or other messages while calling decodePage() - zero
	 * length is no problems
	 */
	public String getPageDecodeReport() {
		return decodeStatus;
	}

	/**
	 * Return String with all error messages from last printed (useful for
	 * debugging)
	 */
	public String getPageFailureMessage() {
		return pageErrorMessages;
	}


	/**
	 * return object which provides access to file images and name
	 */
	public ObjectStore getObjectStore() {
		return objectStoreRef;
	}

	/**
	 * return object which provides access to file images and name (use not
	 * recommended)
	 */
	public void setObjectStore(ObjectStore newStore) {
		objectStoreRef = newStore;
	}


	/** Indicates if embedded fonts supported - only of use in OS version */
	public boolean supportsEmbeddedFonts() {

		return PdfStreamDecoder.embeddedFontsSupported();
	}


	/** get PDF version in file */
	final public String getPDFVersion() {
		return pdfVersion;
	}

	public Map resolveToMapOrString(String string, Object rawAnnotDetails) {

		return (Map) currentPdfFile.resolveToMapOrString(string,
				rawAnnotDetails);
	}

	/** used for non-PDF files to reset page */
	public void resetForNonPDFPage() {

		/** set hires mode or not for display */
		currentDisplay.setHiResImageForDisplayMode(false);

		fontsInFile = "";
		pageCount = 1;
		hasOutline = false;
		// <start-forms>
		if (formsAvailable) {
			if (currentFormRenderer != null)
				currentFormRenderer.removeDisplayComponentsFromScreen(this);

			if ((currentAnnotRenderer != null) && (showAnnotations))
				currentAnnotRenderer.removeDisplayComponentsFromScreen(this);

			invalidate();
		}
		// <end-forms>
		// reset page data
		this.pageData = new PdfPageData();
	}

	/** provides details on printing to enable debugging info for IDRsolutions */
	public void setDebugPrint(boolean debugPrint) {
		this.debugPrint = debugPrint;
	}


	/**
	 * show if page is an XFA form
	 */
	public boolean isXFAForm() {
		return isXFA;
	}

	/**
	 * return page currently being printed or -1 if finished
	 */
	public int getCurrentPrintPage() {
		return currentPrintPage;
	}

	public void resetCurrentPrintPage() {
		currentPrintPage = 0;
	}
	
	/**flag to show if we suspect problem with some images*/
	public boolean hasAllImages() {
		return imagesProcessedFully;
	}
}
