]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Switch for disabling on-the-fly stream output (if something goes wrong. was tested...
authorJeremias Maerki <jeremias@apache.org>
Thu, 27 Mar 2003 11:15:27 +0000 (11:15 +0000)
committerJeremias Maerki <jeremias@apache.org>
Thu, 27 Mar 2003 11:15:27 +0000 (11:15 +0000)
Factory methods moved to new PDFFactory class.
More uniform and centralized registration of PDFObjects (a lot less redundancy!)
Logging via Avalon Logger (Child PDFObjects get their logger via getDocumentSafely().getLogger()).

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@196172 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/fop/pdf/PDFDocument.java

index 7feb2141e0a8d754b7513e470e90fd3533507566..54f2b420b86034cda486553cd71fc866d68862d0 100644 (file)
  */ 
 package org.apache.fop.pdf;
 
-import org.apache.fop.util.StreamUtilities;
-
-import org.apache.avalon.framework.logger.AbstractLogEnabled;
-import org.apache.fop.fonts.CIDFont;
-import org.apache.fop.fonts.CustomFont;
-import org.apache.fop.fonts.Font;
-import org.apache.fop.fonts.FontDescriptor;
-import org.apache.fop.fonts.FontMetrics;
-import org.apache.fop.fonts.FontType;
-import org.apache.fop.fonts.LazyFont;
-import org.apache.fop.fonts.MultiByteFont;
-import org.apache.fop.fonts.truetype.FontFileReader;
-import org.apache.fop.fonts.truetype.TTFSubSetFile;
-import org.apache.fop.fonts.type1.PFBData;
-import org.apache.fop.fonts.type1.PFBParser;
-
 // Java
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
 import java.util.List;
 import java.util.Map;
 import java.util.Iterator;
-import java.awt.geom.Rectangle2D;
+
+// Avalon
+import org.apache.avalon.framework.logger.LogEnabled;
+import org.apache.avalon.framework.logger.Logger;
 
 /* image support modified from work of BoBoGi */
 /* font support based on work by Takayuki Takeuchi */
@@ -99,7 +87,7 @@ import java.awt.geom.Rectangle2D;
  * the object list; enhanced trailer output; cleanups.
  *
  */
-public class PDFDocument extends AbstractLogEnabled {
+public class PDFDocument implements LogEnabled {
     
     private static final Integer LOCATION_PLACEHOLDER = new Integer(0);
     /**
@@ -112,6 +100,8 @@ public class PDFDocument extends AbstractLogEnabled {
      */
     public static final String ENCODING = "ISO-8859-1";
 
+    private Logger logger;
+
     /**
      * the current character position
      */
@@ -133,7 +123,7 @@ public class PDFDocument extends AbstractLogEnabled {
     /**
      * the objects themselves
      */
-    protected List objects = new java.util.ArrayList();
+    protected List objects = new java.util.LinkedList();
 
     /**
      * character position of xref table
@@ -243,6 +233,10 @@ public class PDFDocument extends AbstractLogEnabled {
     protected List gotos = new java.util.ArrayList();
 
 
+    private PDFFactory factory;
+    
+    private boolean encodingOnTheFly = true;
+
     /**
      * creates an empty PDF document <p>
      *
@@ -257,17 +251,68 @@ public class PDFDocument extends AbstractLogEnabled {
      */
     public PDFDocument(String prod) {
 
+        this.factory = new PDFFactory(this);
+
         /* create the /Root, /Info and /Resources objects */
-        this.pages = makePages();
+        this.pages = getFactory().makePages();
 
         // Create the Root object
-        this.root = makeRoot(pages);
+        this.root = getFactory().makeRoot(pages);
 
         // Create the Resources object
-        this.resources = makeResources();
+        this.resources = getFactory().makeResources();
 
         // Make the /Info record
-        this.info = makeInfo(prod);
+        this.info = getFactory().makeInfo(prod);
+    }
+
+    /**
+     * Returns the factory for PDF objects.
+     * @return PDFFactory the factory
+     */
+    public PDFFactory getFactory() {
+        return this.factory;
+    }
+
+    /**
+     * Indicates whether stream encoding on-the-fly is enabled. If enabled
+     * stream can be serialized without the need for a buffer to merely
+     * calculate the stream length.
+     * @return boolean true if on-the-fly encoding is enabled
+     */
+    public boolean isEncodingOnTheFly() {
+        return this.encodingOnTheFly;
+    }
+
+    /**
+     * @see org.apache.avalon.framework.logger.LogEnabled#enableLogging(Logger)
+     */
+    public void enableLogging(Logger logger) {
+        this.logger = logger;
+    }
+
+    /**
+     * Helper method to allow sub-classes to aquire logger.
+     * 
+     * <p>There is no performance penalty as this is a final method
+     * and will be inlined by the JVM.</p>
+     * @return the Logger
+     */
+    protected final Logger getLogger() {
+        return this.logger;
+    }
+
+    /**
+     * Converts text to a byte array for writing to a PDF file.
+     * @param text text to convert/encode
+     * @return byte[] the resulting byte array
+     */
+    public static byte[] encode(String text) {
+        try {
+            return text.getBytes(ENCODING);
+        } catch (UnsupportedEncodingException uee) {
+            return text.getBytes();
+        }       
     }
 
     /**
@@ -294,7 +339,7 @@ public class PDFDocument extends AbstractLogEnabled {
      * @param map the map of filter lists for each stream type
      */
     public void setFilterMap(Map map) {
-        filterMap = map;
+        this.filterMap = map;
     }
 
     /**
@@ -303,58 +348,15 @@ public class PDFDocument extends AbstractLogEnabled {
      * @return the map of filters being used
      */
     public Map getFilterMap() {
-        return filterMap;
+        return this.filterMap;
     }
 
     /**
-     * Apply the encryption filter to a PDFStream if encryption is enabled.
-     * @param stream PDFStream to encrypt
-     */
-    public void applyEncryption(PDFStream stream) {
-        if (isEncryptionActive()) {
-            this.encryption.applyFilter(stream);
-        }
-    }
-
-    /**
-     * Enables PDF encryption.
-     * @param params The encryption parameters for the pdf file
-     */
-    public void setEncryption(PDFEncryptionParams params) {
-        this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params);
-        if (encryption != null) {
-            /**@todo this cast is ugly. PDFObject should be transformed to an interface. */
-            addTrailerObject((PDFObject)this.encryption);
-        } else {
-            getLogger().warn("PDF encryption is unavailable. PDF will be "
-                + "generated without encryption.");
-        }
-    }
-    
-    
-    /**
-     * Indicates whether encryption is active for this PDF or not.
-     * @return boolean True if encryption is active
-     */
-    public boolean isEncryptionActive() {
-        return this.encryption != null;
-    }
-    
-    /**
-     * Make a /Catalog (Root) object. This object is written in
-     * the trailer.
-     *
-     * @param pages the pages pdf object that the root points to
-     * @return the new pdf root object for this document
+     * Returns the PDFPages object associated with the root object.
+     * @return the PDFPages object
      */
-    public PDFRoot makeRoot(PDFPages pages) {
-
-        /*
-        * Make a /Pages object. This object is written in the trailer.
-        */
-        PDFRoot pdfRoot = new PDFRoot(++this.objectcount, pages);
-        addTrailerObject(pdfRoot);
-        return pdfRoot;
+    public PDFPages getPages() {
+        return this.pages;
     }
 
     /**
@@ -367,183 +369,162 @@ public class PDFDocument extends AbstractLogEnabled {
     }
 
     /**
-     * Make a /Pages object. This object is written in the trailer.
+     * Get the pdf info object for this document.
      *
-     * @return a new PDF Pages object for adding pages to
+     * @return the PDF Info object for this document
      */
-    public PDFPages makePages() {
-        PDFPages pdfPages = new PDFPages(++this.objectcount);
-        addTrailerObject(pdfPages);
-        return pdfPages;
+    public PDFInfo getInfo() {
+        return info;
     }
 
     /**
-     * Make a /Resources object. This object is written in the trailer.
-     *
-     * @return a new PDF resources object
+     * Registers a PDFObject in this PDF document. The PDF is assigned a new 
+     * object number.
+     * @param obj PDFObject to add
+     * @return PDFObject the PDFObject added (its object number set)
      */
-    public PDFResources makeResources() {
-        PDFResources pdfResources = new PDFResources(++this.objectcount);
-        addTrailerObject(pdfResources);
-        return pdfResources;
+    public PDFObject registerObject(PDFObject obj) {
+        assignObjectNumber(obj);
+        addObject(obj);
+        return obj;
     }
 
     /**
-     * make an /Info object
-     *
-     * @param prod string indicating application producing the PDF
-     * @return the created /Info object
-     */
-    protected PDFInfo makeInfo(String prod) {
-
-        /*
-         * create a PDFInfo with the next object number and add to
-         * list of objects
-         */
-        PDFInfo pdfInfo = new PDFInfo(++this.objectcount);
-        // set the default producer
-        pdfInfo.setProducer(prod);
-        this.objects.add(pdfInfo);
-        return pdfInfo;
+     * Assigns the PDFObject a object number and sets the parent of the 
+     * PDFObject to this PDFDocument.
+     * @param obj PDFObject to assign a number to
+     */
+    public void assignObjectNumber(PDFObject obj) {
+        if (obj == null) {
+            throw new NullPointerException("obj must not be null");
+        }
+        if (obj.hasObjectNumber()) {
+            throw new IllegalStateException("Error registering a PDFObject: "
+                + "PDFObject already has an object number");
+        }
+        PDFDocument currentParent = obj.getDocument();
+        if (currentParent != null && currentParent != this) {
+            throw new IllegalStateException("Error registering a PDFObject: "
+                + "PDFObject already has a parent PDFDocument");
+        }
+        
+        obj.setObjectNumber(++this.objectcount);
+
+        if (currentParent == null) {
+            obj.setDocument(this);
+        }
     }
 
     /**
-     * Get the pdf info object for this document.
-     *
-     * @return the PDF Info object for this document
+     * Adds an PDFObject to this document. The object must have a object number
+     * assigned.
+     * @param obj PDFObject to add
      */
-    public PDFInfo getInfo() {
-        return info;
+    public void addObject(PDFObject obj) {
+        if (obj == null) {
+            throw new NullPointerException("obj must not be null");
+        }
+        if (!obj.hasObjectNumber()) {
+            throw new IllegalStateException("Error adding a PDFObject: "
+                + "PDFObject doesn't have an object number");
+        }
+
+        //Add object to list
+        this.objects.add(obj);
+        
+        //System.out.println("Registering: "+obj);
+        
+        //Add object to special lists where necessary
+        if (obj instanceof PDFFunction) {
+            this.functions.add(obj);
+        }
+        if (obj instanceof PDFShading) {
+            final String shadingName = "Sh" + (++this.shadingCount);
+            ((PDFShading)obj).setName(shadingName);
+            this.shadings.add(obj);
+        }
+        if (obj instanceof PDFPattern) {
+            final String patternName = "Pa" + (++this.patternCount);
+            ((PDFPattern)obj).setName(patternName);
+            this.patterns.add(obj);
+        }
+        if (obj instanceof PDFFont) {
+            final PDFFont font = (PDFFont)obj;
+            this.fontMap.put(font.getName(), font);
+        }
+        if (obj instanceof PDFGState) {
+            this.gstates.add(obj);
+        }
+        if (obj instanceof PDFPage) {
+            this.pages.notifyKidRegistered((PDFPage)obj);
+        }
+        if (obj instanceof PDFLink) {
+            this.links.add(obj);
+        }            
+        if (obj instanceof PDFFileSpec) {
+            this.filespecs.add(obj);
+        }
+        if (obj instanceof PDFGoToRemote) {
+            this.gotoremotes.add(obj);
+        }
     }
 
+
     /**
-     * Make a Type 0 sampled function
-     *
-     * @param theDomain List objects of Double objects.
-     * This is the domain of the function.
-     * See page 264 of the PDF 1.3 Spec.
-     * @param theRange List objects of Double objects.
-     * This is the Range of the function.
-     * See page 264 of the PDF 1.3 Spec.
-     * @param theSize A List object of Integer objects.
-     * This is the number of samples in each input dimension.
-     * I can't imagine there being more or less than two input dimensions,
-     * so maybe this should be an array of length 2.
-     *
-     * See page 265 of the PDF 1.3 Spec.
-     * @param theBitsPerSample An int specifying the number of bits user
-     *                    to represent each sample value.
-     * Limited to 1,2,4,8,12,16,24 or 32.
-     * See page 265 of the 1.3 PDF Spec.
-     * @param theOrder The order of interpolation between samples.
-     *                 Default is 1 (one). Limited
-     * to 1 (one) or 3, which means linear or cubic-spline interpolation.
-     *
-     * This attribute is optional.
-     *
-     * See page 265 in the PDF 1.3 spec.
-     * @param theEncode List objects of Double objects.
-     * This is the linear mapping of input values intop the domain
-     * of the function's sample table. Default is hard to represent in
-     * ascii, but basically [0 (Size0 1) 0 (Size1 1)...].
-     * This attribute is optional.
-     *
-     * See page 265 in the PDF 1.3 spec.
-     * @param theDecode List objects of Double objects.
-     * This is a linear mapping of sample values into the range.
-     * The default is just the range.
-     *
-     * This attribute is optional.
-     * Read about it on page 265 of the PDF 1.3 spec.
-     * @param theFunctionDataStream The sample values that specify
-     *                        the function are provided in a stream.
-     *
-     * This is optional, but is almost always used.
-     *
-     * Page 265 of the PDF 1.3 spec has more.
-     * @param theFilter This is a vector of String objects which
-     *                  are the various filters that have are to be
-     *                  applied to the stream to make sense of it.
-     *                  Order matters, so watch out.
+     * Add trailer object.
+     * Adds an object to the list of trailer objects.
      *
-     * This is not documented in the Function section of the PDF 1.3 spec,
-     * it was deduced from samples that this is sometimes used, even if we may never
-     * use it in FOP. It is added for completeness sake.
-     * @param theFunctionType This is the type of function (0,2,3, or 4).
-     * It should be 0 as this is the constructor for sampled functions.
-     * @return the PDF function that was created
-     */
-    public PDFFunction makeFunction(int theFunctionType, List theDomain,
-                                    List theRange, List theSize,
-                                    int theBitsPerSample, int theOrder,
-                                    List theEncode, List theDecode,
-                                    StringBuffer theFunctionDataStream,
-                                    List theFilter) {
-        // Type 0 function
-        PDFFunction function = new PDFFunction(++this.objectcount,
-                                               theFunctionType, theDomain,
-                                               theRange, theSize,
-                                               theBitsPerSample, theOrder,
-                                               theEncode, theDecode,
-                                               theFunctionDataStream,
-                                               theFilter);
-
-        PDFFunction oldfunc = findFunction(function);
-        if (oldfunc == null) {
-            functions.add(function);
-            this.objects.add(function);
-        } else {
-            this.objectcount--;
-            function = oldfunc;
-        }
+     * @param obj the PDF object to add
+     */
+    public void addTrailerObject(PDFObject obj) {
+        this.trailerObjects.add(obj);
+        
+        if (obj instanceof PDFGoTo) {
+            this.gotos.add(obj);
+        }            
+    }
 
-        return (function);
+    /**
+     * Apply the encryption filter to a PDFStream if encryption is enabled.
+     * @param stream PDFStream to encrypt
+     */
+    public void applyEncryption(AbstractPDFStream stream) {
+        if (isEncryptionActive()) {
+            this.encryption.applyFilter(stream);
+        }
     }
 
     /**
-     * make a type Exponential interpolation function
-     * (for shading usually)
-     *
-     * @param theDomain List objects of Double objects.
-     * This is the domain of the function.
-     * See page 264 of the PDF 1.3 Spec.
-     * @param theRange List of Doubles that is the Range of the function.
-     * See page 264 of the PDF 1.3 Spec.
-     * @param theCZero This is a vector of Double objects which defines the function result
-     * when x=0.
-     *
-     * This attribute is optional.
-     * It's described on page 268 of the PDF 1.3 spec.
-     * @param theCOne This is a vector of Double objects which defines the function result
-     * when x=1.
-     *
-     * This attribute is optional.
-     * It's described on page 268 of the PDF 1.3 spec.
-     * @param theInterpolationExponentN This is the inerpolation exponent.
-     *
-     * This attribute is required.
-     * PDF Spec page 268
-     * @param theFunctionType The type of the function, which should be 2.
-     * @return the PDF function that was created
-     */
-    public PDFFunction makeFunction(int theFunctionType, List theDomain,
-                                    List theRange, List theCZero,
-                                    List theCOne,
-                                    double theInterpolationExponentN) {    // type 2
-        PDFFunction function = new PDFFunction(++this.objectcount,
-                                               theFunctionType, theDomain,
-                                               theRange, theCZero, theCOne,
-                                               theInterpolationExponentN);
-        PDFFunction oldfunc = findFunction(function);
-        if (oldfunc == null) {
-            functions.add(function);
-            this.objects.add(function);
+     * Enables PDF encryption.
+     * @param params The encryption parameters for the pdf file
+     */
+    public void setEncryption(PDFEncryptionParams params) {
+        this.encryption = PDFEncryptionManager.newInstance(++this.objectcount, params);
+        ((PDFObject)this.encryption).setDocument(this);
+        if (encryption != null) {
+            /**@todo this cast is ugly. PDFObject should be transformed to an interface. */
+            addTrailerObject((PDFObject)this.encryption);
         } else {
-            this.objectcount--;
-            function = oldfunc;
+            getLogger().warn("PDF encryption is unavailable. PDF will be "
+                + "generated without encryption.");
         }
-
-        return (function);
+    }
+    
+    
+    /**
+     * Indicates whether encryption is active for this PDF or not.
+     * @return boolean True if encryption is active
+     */
+    public boolean isEncryptionActive() {
+        return this.encryption != null;
+    }
+    
+    /**
+     * Returns the active Encryption object.
+     * @return the Encryption object
+     */
+    public PDFEncryption getEncryption() {
+        return encryption;
     }
 
     private Object findPDFObject(List list, PDFObject compare) {
@@ -556,11 +537,23 @@ public class PDFDocument extends AbstractLogEnabled {
         return null;
     }
 
-    private PDFFunction findFunction(PDFFunction compare) {
+    /**
+     * Looks through the registered functions to see if one that is equal to
+     * a reference object exists
+     * @param compare reference object
+     * @return the function if it was found, null otherwise
+     */
+    protected PDFFunction findFunction(PDFFunction compare) {
         return (PDFFunction)findPDFObject(functions, compare);
     }
 
-    private PDFShading findShading(PDFShading compare) {
+    /**
+     * Looks through the registered shadings to see if one that is equal to
+     * a reference object exists
+     * @param compare reference object
+     * @return the shading if it was found, null otherwise
+     */
+    protected PDFShading findShading(PDFShading compare) {
         return (PDFShading)findPDFObject(shadings, compare);
     }
 
@@ -569,429 +562,86 @@ public class PDFDocument extends AbstractLogEnabled {
      * The problem with this is for tiling patterns the pattern
      * data stream is stored and may use up memory, usually this
      * would only be a small amount of data.
+     * @param compare reference object
+     * @return the shading if it was found, null otherwise
      */
-    private PDFPattern findPattern(PDFPattern compare) {
+    protected PDFPattern findPattern(PDFPattern compare) {
         return (PDFPattern)findPDFObject(patterns, compare);
     }
 
     /**
-     * Make a Type 3 Stitching function
-     *
-     * @param theDomain List objects of Double objects.
-     * This is the domain of the function.
-     * See page 264 of the PDF 1.3 Spec.
-     * @param theRange List objects of Double objects.
-     * This is the Range of the function.
-     * See page 264 of the PDF 1.3 Spec.
-     * @param theFunctions An List of the PDFFunction objects
-     *                     that the stitching function stitches.
-     *
-     * This attributed is required.
-     * It is described on page 269 of the PDF spec.
-     * @param theBounds This is a vector of Doubles representing
-     *                  the numbers that, in conjunction with Domain
-     *                  define the intervals to which each function from
-     *                  the 'functions' object applies. It must be in
-     *                  order of increasing magnitude, and each must be
-     *                  within Domain.
-     *
-     * It basically sets how much of the gradient each function handles.
-     *
-     * This attributed is required.
-     * It's described on page 269 of the PDF 1.3 spec.
-     * @param theEncode List objects of Double objects.
-     * This is the linear mapping of input values intop the domain
-     * of the function's sample table. Default is hard to represent in
-     * ascii, but basically [0 (Size0 1) 0 (Size1 1)...].
-     * This attribute is required.
-     *
-     * See page 270 in the PDF 1.3 spec.
-     * @param theFunctionType This is the function type. It should be 3,
-     * for a stitching function.
-     * @return the PDF function that was created
-     */
-    public PDFFunction makeFunction(int theFunctionType, List theDomain,
-                                    List theRange, List theFunctions,
-                                    List theBounds,
-                                    List theEncode) {
-        // Type 3
-
-        PDFFunction function = new PDFFunction(++this.objectcount,
-                                               theFunctionType, theDomain,
-                                               theRange, theFunctions,
-                                               theBounds, theEncode);
-
-        PDFFunction oldfunc = findFunction(function);
-        if (oldfunc == null) {
-            functions.add(function);
-            this.objects.add(function);
-        } else {
-            this.objectcount--;
-            function = oldfunc;
-        }
-
-        return (function);
-    }
-
-    /**
-     * make a postscript calculator function
-     *
-     * @param theNumber the PDF object number
-     * @param theFunctionType the type of function to make
-     * @param theDomain the domain values
-     * @param theRange the range values of the function
-     * @param theFunctionDataStream a string containing the pdf drawing
-     * @return the PDF function that was created
-     */
-    public PDFFunction makeFunction(int theNumber, int theFunctionType,
-                                    List theDomain, List theRange,
-                                    StringBuffer theFunctionDataStream) {
-        // Type 4
-        PDFFunction function = new PDFFunction(++this.objectcount,
-                                               theFunctionType, theDomain,
-                                               theRange,
-                                               theFunctionDataStream);
-
-        PDFFunction oldfunc = findFunction(function);
-        if (oldfunc == null) {
-            functions.add(function);
-            this.objects.add(function);
-        } else {
-            this.objectcount--;
-            function = oldfunc;
-        }
-
-        return (function);
-
+     * Finds a font.
+     * @param fontname name of the font
+     * @return PDFFont the requested font, null if it wasn't found
+     */
+    protected PDFFont findFont(String fontname) {
+        return (PDFFont)fontMap.get(fontname);
     }
 
     /**
-     * make a function based shading object
-     *
-     * @param res the PDF resource context to add the shading, may be null
-     * @param theShadingType The type of shading object, which should be 1 for function
-     * based shading.
-     * @param theColorSpace The colorspace is 'DeviceRGB' or something similar.
-     * @param theBackground An array of color components appropriate to the
-     * colorspace key specifying a single color value.
-     * This key is used by the f operator buy ignored by the sh operator.
-     * @param theBBox List of double's representing a rectangle
-     * in the coordinate space that is current at the
-     * time of shading is imaged. Temporary clipping
-     * boundary.
-     * @param theAntiAlias Whether or not to anti-alias.
-     * @param theDomain Optional vector of Doubles specifying the domain.
-     * @param theMatrix List of Doubles specifying the matrix.
-     * If it's a pattern, then the matrix maps it to pattern space.
-     * If it's a shading, then it maps it to current user space.
-     * It's optional, the default is the identity matrix
-     * @param theFunction The PDF Function that maps an (x,y) location to a color
-     * @return the PDF shading that was created
-     */
-    public PDFShading makeShading(PDFResourceContext res, int theShadingType,
-                                  PDFColorSpace theColorSpace,
-                                  List theBackground, List theBBox,
-                                  boolean theAntiAlias, List theDomain,
-                                  List theMatrix,
-                                  PDFFunction theFunction) {
-        // make Shading of Type 1
-        String theShadingName = new String("Sh" + (++this.shadingCount));
-
-        PDFShading shading = new PDFShading(++this.objectcount,
-                                            theShadingName, theShadingType,
-                                            theColorSpace, theBackground,
-                                            theBBox, theAntiAlias, theDomain,
-                                            theMatrix, theFunction);
-
-        PDFShading oldshad = findShading(shading);
-        if (oldshad == null) {
-            shadings.add(shading);
-            this.objects.add(shading);
-        } else {
-            this.objectcount--;
-            this.shadingCount--;
-            shading = oldshad;
-        }
-
-        // add this shading to resources
-        if (res != null) {
-            res.getPDFResources().addShading(shading);
-        } else {
-            this.resources.addShading(shading);
-        }
-
-        return (shading);
+     * Finds a link.
+     * @param compare reference object to use as search template
+     * @return the link if found, null otherwise
+     */
+    protected PDFLink findLink(PDFLink compare) {
+        return (PDFLink)findPDFObject(links, compare);
     }
 
     /**
-     * Make an axial or radial shading object.
-     *
-     * @param res the PDF resource context to add the shading, may be null
-     * @param theShadingType 2 or 3 for axial or radial shading
-     * @param theColorSpace "DeviceRGB" or similar.
-     * @param theBackground theBackground An array of color components appropriate to the
-     * colorspace key specifying a single color value.
-     * This key is used by the f operator buy ignored by the sh operator.
-     * @param theBBox List of double's representing a rectangle
-     * in the coordinate space that is current at the
-     * time of shading is imaged. Temporary clipping
-     * boundary.
-     * @param theAntiAlias Default is false
-     * @param theCoords List of four (type 2) or 6 (type 3) Double
-     * @param theDomain List of Doubles specifying the domain
-     * @param theFunction the Stitching (PDFfunction type 3) function,
-     *                    even if it's stitching a single function
-     * @param theExtend List of Booleans of whether to extend the
-     *                  start and end colors past the start and end points
-     * The default is [false, false]
-     * @return the PDF shading that was created
-     */
-    public PDFShading makeShading(PDFResourceContext res, int theShadingType,
-                                  PDFColorSpace theColorSpace,
-                                  List theBackground, List theBBox,
-                                  boolean theAntiAlias, List theCoords,
-                                  List theDomain, PDFFunction theFunction,
-                                  List theExtend) {
-        // make Shading of Type 2 or 3
-        String theShadingName = new String("Sh" + (++this.shadingCount));
-
-        PDFShading shading = new PDFShading(++this.objectcount,
-                                            theShadingName, theShadingType,
-                                            theColorSpace, theBackground,
-                                            theBBox, theAntiAlias, theCoords,
-                                            theDomain, theFunction,
-                                            theExtend);
-
-        PDFShading oldshad = findShading(shading);
-        if (oldshad == null) {
-            shadings.add(shading);
-            this.objects.add(shading);
-        } else {
-            this.objectcount--;
-            this.shadingCount--;
-            shading = oldshad;
-        }
-
-        if (res != null) {
-            res.getPDFResources().addShading(shading);
-        } else {
-            this.resources.addShading(shading);
-        }
-
-        return (shading);
+     * Finds a file spec.
+     * @param compare reference object to use as search template
+     * @return the file spec if found, null otherwise
+     */
+    protected PDFFileSpec findFileSpec(PDFFileSpec compare) {
+        return (PDFFileSpec)findPDFObject(filespecs, compare);
     }
 
     /**
-     * Make a free-form gouraud shaded triangle mesh, coons patch mesh, or tensor patch mesh
-     * shading object
-     *
-     * @param res the PDF resource context to add the shading, may be null
-     * @param theShadingType 4, 6, or 7 depending on whether it's
-     * Free-form gouraud-shaded triangle meshes, coons patch meshes,
-     * or tensor product patch meshes, respectively.
-     * @param theColorSpace "DeviceRGB" or similar.
-     * @param theBackground theBackground An array of color components appropriate to the
-     * colorspace key specifying a single color value.
-     * This key is used by the f operator buy ignored by the sh operator.
-     * @param theBBox List of double's representing a rectangle
-     * in the coordinate space that is current at the
-     * time of shading is imaged. Temporary clipping
-     * boundary.
-     * @param theAntiAlias Default is false
-     * @param theBitsPerCoordinate 1,2,4,8,12,16,24 or 32.
-     * @param theBitsPerComponent 1,2,4,8,12, and 16
-     * @param theBitsPerFlag 2,4,8.
-     * @param theDecode List of Doubles see PDF 1.3 spec pages 303 to 312.
-     * @param theFunction the PDFFunction
-     * @return the PDF shading that was created
-     */
-    public PDFShading makeShading(PDFResourceContext res, int theShadingType,
-                                  PDFColorSpace theColorSpace,
-                                  List theBackground, List theBBox,
-                                  boolean theAntiAlias,
-                                  int theBitsPerCoordinate,
-                                  int theBitsPerComponent,
-                                  int theBitsPerFlag, List theDecode,
-                                  PDFFunction theFunction) {
-        // make Shading of type 4,6 or 7
-        String theShadingName = new String("Sh" + (++this.shadingCount));
-
-        PDFShading shading = new PDFShading(++this.objectcount,
-                                            theShadingName, theShadingType,
-                                            theColorSpace, theBackground,
-                                            theBBox, theAntiAlias,
-                                            theBitsPerCoordinate,
-                                            theBitsPerComponent,
-                                            theBitsPerFlag, theDecode,
-                                            theFunction);
-
-        PDFShading oldshad = findShading(shading);
-        if (oldshad == null) {
-            shadings.add(shading);
-            this.objects.add(shading);
-        } else {
-            this.objectcount--;
-            this.shadingCount--;
-            shading = oldshad;
-        }
-
-        if (res != null) {
-            res.getPDFResources().addShading(shading);
-        } else {
-            this.resources.addShading(shading);
-        }
-
-        return (shading);
+     * Finds a goto remote.
+     * @param compare reference object to use as search template
+     * @return the goto remote if found, null otherwise
+     */
+    protected PDFGoToRemote findGoToRemote(PDFGoToRemote compare) {
+        return (PDFGoToRemote)findPDFObject(gotoremotes, compare);
     }
 
     /**
-     * make a Lattice-Form Gouraud mesh shading object
-     *
-     * @param res the PDF resource context to add the shading, may be null
-     * @param theShadingType 5 for lattice-Form Gouraud shaded-triangle mesh
-     * without spaces. "Shading1" or "Sh1" are good examples.
-     * @param theColorSpace "DeviceRGB" or similar.
-     * @param theBackground theBackground An array of color components appropriate to the
-     * colorspace key specifying a single color value.
-     * This key is used by the f operator buy ignored by the sh operator.
-     * @param theBBox List of double's representing a rectangle
-     * in the coordinate space that is current at the
-     * time of shading is imaged. Temporary clipping
-     * boundary.
-     * @param theAntiAlias Default is false
-     * @param theBitsPerCoordinate 1,2,4,8,12,16, 24, or 32
-     * @param theBitsPerComponent 1,2,4,8,12,24,32
-     * @param theDecode List of Doubles. See page 305 in PDF 1.3 spec.
-     * @param theVerticesPerRow number of vertices in each "row" of the lattice.
-     * @param theFunction The PDFFunction that's mapped on to this shape
-     * @return the PDF shading that was created
-     */
-    public PDFShading makeShading(PDFResourceContext res, int theShadingType,
-                                  PDFColorSpace theColorSpace,
-                                  List theBackground, List theBBox,
-                                  boolean theAntiAlias,
-                                  int theBitsPerCoordinate,
-                                  int theBitsPerComponent, List theDecode,
-                                  int theVerticesPerRow,
-                                  PDFFunction theFunction) {
-        // make shading of Type 5
-        String theShadingName = new String("Sh" + (++this.shadingCount));
-
-        PDFShading shading = new PDFShading(++this.objectcount,
-                                            theShadingName, theShadingType,
-                                            theColorSpace, theBackground,
-                                            theBBox, theAntiAlias,
-                                            theBitsPerCoordinate,
-                                            theBitsPerComponent, theDecode,
-                                            theVerticesPerRow, theFunction);
-
-        PDFShading oldshad = findShading(shading);
-        if (oldshad == null) {
-            shadings.add(shading);
-            this.objects.add(shading);
-        } else {
-            this.objectcount--;
-            this.shadingCount--;
-            shading = oldshad;
-        }
-
-        if (res != null) {
-            res.getPDFResources().addShading(shading);
-        } else {
-            this.resources.addShading(shading);
-        }
-
-        return (shading);
+     * Finds a goto.
+     * @param compare reference object to use as search template
+     * @return the goto if found, null otherwise
+     */
+    protected PDFGoTo findGoTo(PDFGoTo compare) {
+        return (PDFGoTo)findPDFObject(gotos, compare);
     }
-
+    
     /**
-     * Make a tiling pattern
-     *
-     * @param res the PDF resource context to add the shading, may be null
-     * @param thePatternType the type of pattern, which is 1 for tiling.
-     * @param theResources the resources associated with this pattern
-     * @param thePaintType 1 or 2, colored or uncolored.
-     * @param theTilingType 1, 2, or 3, constant spacing, no distortion, or faster tiling
-     * @param theBBox List of Doubles: The pattern cell bounding box
-     * @param theXStep horizontal spacing
-     * @param theYStep vertical spacing
-     * @param theMatrix Optional List of Doubles transformation matrix
-     * @param theXUID Optional vector of Integers that uniquely identify the pattern
-     * @param thePatternDataStream The stream of pattern data to be tiled.
-     * @return the PDF pattern that was created
-     */
-    public PDFPattern makePattern(PDFResourceContext res, int thePatternType,    // 1
-                                  PDFResources theResources, int thePaintType, int theTilingType,
-                                  List theBBox, double theXStep,
-                                  double theYStep, List theMatrix,
-                                  List theXUID, StringBuffer thePatternDataStream) {
-        String thePatternName = new String("Pa" + (++this.patternCount));
-        // int theNumber, String thePatternName,
-        // PDFResources theResources
-        PDFPattern pattern = new PDFPattern(++this.objectcount,
-                                            thePatternName, theResources, 1,
-                                            thePaintType, theTilingType,
-                                            theBBox, theXStep, theYStep,
-                                            theMatrix, theXUID,
-                                            thePatternDataStream);
-
-        PDFPattern oldpatt = findPattern(pattern);
-        if (oldpatt == null) {
-            patterns.add(pattern);
-            this.objects.add(pattern);
-        } else {
-            this.objectcount--;
-            this.patternCount--;
-            pattern = oldpatt;
-        }
-
-        if (res != null) {
-            res.getPDFResources().addPattern(pattern);
-        } else {
-            this.resources.addPattern(pattern);
+     * Looks for an existing GState to use
+     * @param wanted requested features
+     * @param current currently active features
+     * @return PDFGState the GState if found, null otherwise
+     */
+    protected PDFGState findGState(PDFGState wanted, PDFGState current) {
+        PDFGState poss;
+        Iterator iter = gstates.iterator();
+        while (iter.hasNext()) {
+            PDFGState avail = (PDFGState)iter.next();
+            poss = new PDFGState();
+            poss.addValues(current);
+            poss.addValues(avail);
+            if (poss.equals(wanted)) {
+                return avail;
+            }
         }
-
-        return (pattern);
+        return null;
     }
 
     /**
-     * Make a smooth shading pattern
+     * Get the PDF color space object.
      *
-     * @param res the PDF resource context to add the shading, may be null
-     * @param thePatternType the type of the pattern, which is 2, smooth shading
-     * @param theShading the PDF Shading object that comprises this pattern
-     * @param theXUID optional:the extended unique Identifier if used.
-     * @param theExtGState optional: the extended graphics state, if used.
-     * @param theMatrix Optional:List of Doubles that specify the matrix.
-     * @return the PDF pattern that was created       
-     */
-    public PDFPattern makePattern(PDFResourceContext res,
-                                  int thePatternType, PDFShading theShading,
-                                  List theXUID, StringBuffer theExtGState,
-                                  List theMatrix) {
-        String thePatternName = new String("Pa" + (++this.patternCount));
-
-        PDFPattern pattern = new PDFPattern(++this.objectcount,
-                                            thePatternName, 2, theShading,
-                                            theXUID, theExtGState, theMatrix);
-
-        PDFPattern oldpatt = findPattern(pattern);
-        if (oldpatt == null) {
-            patterns.add(pattern);
-            this.objects.add(pattern);
-        } else {
-            this.objectcount--;
-            this.patternCount--;
-            pattern = oldpatt;
-        }
-
-        if (res != null) {
-            res.getPDFResources().addPattern(pattern);
-        } else {
-            this.resources.addPattern(pattern);
-        }
-
-        return (pattern);
+     * @return the color space
+     */
+    public PDFColorSpace getPDFColorSpace() {
+        return this.colorspace;
     }
 
     /**
@@ -1000,7 +650,7 @@ public class PDFDocument extends AbstractLogEnabled {
      * @return the color space
      */
     public int getColorSpace() {
-        return (this.colorspace.getColorSpace());
+        return getPDFColorSpace().getColorSpace();
     }
 
     /**
@@ -1014,132 +664,6 @@ public class PDFDocument extends AbstractLogEnabled {
         return;
     }
 
-    /**
-     * Make a gradient
-     *  
-     * @param res the PDF resource context to add the shading, may be null
-     * @param radial if true a radial gradient will be created
-     * @param theColorspace the colorspace of the gradient
-     * @param theColors the list of colors for the gradient
-     * @param theBounds the list of bounds associated with the colors
-     * @param theCoords the coordinates for the gradient
-     * @return the PDF pattern that was created
-     */
-    public PDFPattern createGradient(PDFResourceContext res, boolean radial,
-                                     PDFColorSpace theColorspace,
-                                     List theColors, List theBounds,
-                                     List theCoords) {
-        PDFShading myShad;
-        PDFFunction myfunky;
-        PDFFunction myfunc;
-        List theCzero;
-        List theCone;
-        PDFPattern myPattern;
-        //PDFColorSpace theColorSpace;
-        double interpolation = (double)1.000;
-        List theFunctions = new java.util.ArrayList();
-
-        int currentPosition;
-        int lastPosition = theColors.size() - 1;
-
-
-        // if 5 elements, the penultimate element is 3.
-        // do not go beyond that, because you always need
-        // to have a next color when creating the function.
-
-        for (currentPosition = 0; currentPosition < lastPosition;
-                currentPosition++) {    // for every consecutive color pair
-            PDFColor currentColor =
-                (PDFColor)theColors.get(currentPosition);
-            PDFColor nextColor = (PDFColor)theColors.get(currentPosition
-                                 + 1);
-            // colorspace must be consistant
-            if (this.colorspace.getColorSpace()
-                    != currentColor.getColorSpace()) {
-                currentColor.setColorSpace(this.colorspace.getColorSpace());
-            }
-
-            if (this.colorspace.getColorSpace() != nextColor.getColorSpace()) {
-                nextColor.setColorSpace(this.colorspace.getColorSpace());
-            }
-
-            theCzero = currentColor.getVector();
-            theCone = nextColor.getVector();
-
-            myfunc = this.makeFunction(2, null, null, theCzero, theCone,
-                                       interpolation);
-
-            theFunctions.add(myfunc);
-
-        }                               // end of for every consecutive color pair
-
-        myfunky = this.makeFunction(3, null, null, theFunctions, theBounds,
-                                    null);
-
-        if (radial) {
-            if (theCoords.size() == 6) {
-                myShad = this.makeShading(res, 3, this.colorspace, null, null,
-                                          false, theCoords, null, myfunky,
-                                          null);
-            } else {    // if the center x, center y, and radius specifiy
-                // the gradient, then assume the same center x, center y,
-                // and radius of zero for the other necessary component
-                List newCoords = new java.util.ArrayList();
-                newCoords.add(theCoords.get(0));
-                newCoords.add(theCoords.get(1));
-                newCoords.add(theCoords.get(2));
-                newCoords.add(theCoords.get(0));
-                newCoords.add(theCoords.get(1));
-                newCoords.add(new Double(0.0));
-
-                myShad = this.makeShading(res, 3, this.colorspace, null, null,
-                                          false, newCoords, null, myfunky,
-                                          null);
-
-            }
-        } else {
-            myShad = this.makeShading(res, 2, this.colorspace, null, null, false,
-                                      theCoords, null, myfunky, null);
-
-        }
-
-        myPattern = this.makePattern(res, 2, myShad, null, null, null);
-
-        return (myPattern);
-    }
-
-
-    /**
-     * make a /Encoding object
-     *
-     * @param encodingName character encoding scheme name
-     * @return the created /Encoding object
-     */
-    public PDFEncoding makeEncoding(String encodingName) {
-
-        /*
-         * create a PDFEncoding with the next object number and add to the
-         * list of objects
-         */
-        PDFEncoding encoding = new PDFEncoding(++this.objectcount,
-                                               encodingName);
-        this.objects.add(encoding);
-        return encoding;
-    }
-
-    /**
-     * Create a PDFICCStream
-     * @see PDFXObject
-     * @see org.apache.fop.image.JpegImage
-     * @see org.apache.fop.pdf.PDFColorSpace
-     * @return the new PDF ICC stream object
-     */
-    public PDFICCStream makePDFICCStream() {
-        PDFICCStream iccStream = new PDFICCStream(++this.objectcount);
-        this.objects.add(iccStream);
-        return iccStream;
-    }
-
     /**
      * Get the font map for this document.
      *
@@ -1149,149 +673,6 @@ public class PDFDocument extends AbstractLogEnabled {
         return fontMap;
     }
 
-    /**
-     * make a Type1 /Font object
-     *
-     * @param fontname internal name to use for this font (eg "F1")
-     * @param basefont name of the base font (eg "Helvetica")
-     * @param encoding character encoding scheme used by the font
-     * @param metrics additional information about the font
-     * @param descriptor additional information about the font
-     * @return the created /Font object
-     */
-    public PDFFont makeFont(String fontname, String basefont,
-                            String encoding, FontMetrics metrics,
-                            FontDescriptor descriptor) {
-        if (fontMap.containsKey(fontname)) {
-            return (PDFFont)fontMap.get(fontname);
-        }
-
-        /*
-         * create a PDFFont with the next object number and add to the
-         * list of objects
-         */
-        if (descriptor == null) {
-            PDFFont font = new PDFFont(++this.objectcount, fontname,
-                                       FontType.TYPE1, basefont, encoding);
-            this.objects.add(font);
-            fontMap.put(fontname, font);
-            return font;
-        } else {
-            FontType fonttype = metrics.getFontType();
-
-            PDFFontDescriptor pdfdesc = makeFontDescriptor(descriptor);
-
-            PDFFontNonBase14 font = null;
-            if (fonttype == FontType.TYPE0) {
-                /*
-                 * Temporary commented out - customized CMaps
-                 * isn't needed until /ToUnicode support is added
-                 * PDFCMap cmap = new PDFCMap(++this.objectcount,
-                 * "fop-ucs-H",
-                 * new PDFCIDSystemInfo("Adobe",
-                 * "Identity",
-                 * 0));
-                 * cmap.addContents();
-                 * this.objects.add(cmap);
-                 */
-                font =
-                    (PDFFontNonBase14)PDFFont.createFont(++this.objectcount,
-                                                         fontname, fonttype,
-                                                         basefont,
-                                                         "Identity-H");
-            } else {
-
-                font =
-                    (PDFFontNonBase14)PDFFont.createFont(++this.objectcount,
-                                                         fontname, fonttype,
-                                                         basefont, encoding);
-            }
-            this.objects.add(font);
-
-            font.setDescriptor(pdfdesc);
-
-            if (fonttype == FontType.TYPE0) {
-                CIDFont cidMetrics;
-                if (metrics instanceof LazyFont) {
-                    cidMetrics = (CIDFont)((LazyFont) metrics).getRealFont();
-                } else {
-                    cidMetrics = (CIDFont)metrics;
-                }
-                PDFCIDSystemInfo sysInfo =
-                    new PDFCIDSystemInfo(cidMetrics.getRegistry(),
-                                         cidMetrics.getOrdering(),
-                                         cidMetrics.getSupplement());
-                PDFCIDFont cidFont =
-                    new PDFCIDFont(++this.objectcount, basefont,
-                                   cidMetrics.getCIDType(),
-                                   cidMetrics.getDefaultWidth(),
-                                   cidMetrics.getWidths(), sysInfo,
-                                   (PDFCIDFontDescriptor)pdfdesc);
-                this.objects.add(cidFont);
-
-                ((PDFFontType0)font).setDescendantFonts(cidFont);
-            } else {
-                int firstChar = 0;
-                int lastChar = 255;
-                if (metrics instanceof CustomFont) {
-                    CustomFont cf = (CustomFont)metrics;
-                    firstChar = cf.getFirstChar();
-                    lastChar = cf.getLastChar();
-                }
-                font.setWidthMetrics(firstChar,
-                                     lastChar,
-                                     makeArray(metrics.getWidths()));
-            }
-
-            fontMap.put(fontname, font);
-
-            return font;
-        }
-    }
-
-
-    /**
-     * make a /FontDescriptor object
-     *
-     * @param desc the font descriptor
-     * @return the new PDF font descriptor
-     */
-    public PDFFontDescriptor makeFontDescriptor(FontDescriptor desc) {
-        PDFFontDescriptor font = null;
-
-        if (desc.getFontType() == FontType.TYPE0) {
-            // CID Font
-            font = new PDFCIDFontDescriptor(++this.objectcount,
-                                            desc.getFontName(),
-                                            desc.getFontBBox(),
-                                            desc.getCapHeight(), desc.getFlags(),
-                                            desc.getItalicAngle(),
-                                            desc.getStemV(), null);
-        } else {
-            // Create normal FontDescriptor
-            font = new PDFFontDescriptor(++this.objectcount, desc.getFontName(),
-                                         desc.getAscender(),
-                                         desc.getDescender(),
-                                         desc.getCapHeight(),
-                                         desc.getFlags(),
-                                         new PDFRectangle(desc.getFontBBox()),
-                                         desc.getStemV(),
-                                         desc.getItalicAngle());
-        }
-        this.objects.add(font);
-
-        // Check if the font is embeddable
-        if (desc.isEmbeddable()) {
-            PDFStream stream = makeFontFile(this.objectcount + 1, desc);
-            if (stream != null) {
-                this.objectcount++;
-                font.setFontFile(desc.getFontType(), stream);
-                this.objects.add(stream);
-            }
-        }
-        return font;
-    }
-
     /**
      * Resolve a URI.
      *
@@ -1310,167 +691,6 @@ public class PDFDocument extends AbstractLogEnabled {
         }
     }
 
-    /**
-     * Embeds a font.
-     * @param obj PDF object number to use
-     * @param desc FontDescriptor of the font.
-     * @return PDFStream The embedded font file
-     */
-    public PDFStream makeFontFile(int obj, FontDescriptor desc) {
-        if (desc.getFontType() == FontType.OTHER) {
-            throw new IllegalArgumentException("Trying to embed unsupported font type: "
-                                                + desc.getFontType());
-        } 
-        
-        Font tempFont;
-        if (desc instanceof LazyFont) {
-            tempFont = ((LazyFont)desc).getRealFont();
-        } else {
-            tempFont = (Font)desc;
-        }
-        if (!(tempFont instanceof CustomFont)) {
-            throw new IllegalArgumentException(
-                      "FontDescriptor must be instance of CustomFont, but is a "
-                       + desc.getClass().getName());
-        }
-        CustomFont font = (CustomFont)tempFont;
-        
-        InputStream in = null;
-        try {
-            // Get file first
-            if (font.getEmbedFileName() != null) {
-                try {
-                    in = resolveURI(font.getEmbedFileName());
-                } catch (Exception e) {
-                    getLogger().error("Failed to embed fontfile: "
-                                       + font.getEmbedFileName() 
-                                       + "(" + e.getMessage() + ")");
-                }
-            }
-    
-            // Get resource
-            if (in == null && font.getEmbedResourceName() != null) {
-                try {
-                    in = new java.io.BufferedInputStream(
-                            this.getClass().getResourceAsStream(font.getEmbedResourceName()));
-                } catch (Exception e) {
-                    getLogger().error("Failed to embed fontresource: "
-                                       + font.getEmbedResourceName()
-                                       + "(" + e.getMessage() + ")");
-                }
-            }
-    
-            if (in == null) {
-                return null;
-            } else {
-                try {
-                    PDFStream embeddedFont;
-                    if (desc.getFontType() == FontType.TYPE0) {
-                        MultiByteFont mbfont = (MultiByteFont)font;
-                        FontFileReader reader = new FontFileReader(in);
-
-                        TTFSubSetFile subset = new TTFSubSetFile();
-                        setupLogger(subset);
-        
-                        byte[] subsetFont = subset.readFont(reader,
-                                             mbfont.getTTCName(), mbfont.getUsedGlyphs());
-                        // Only TrueType CID fonts are supported now
-
-                        embeddedFont = new PDFTTFStream(obj, subsetFont.length);
-                        ((PDFTTFStream)embeddedFont).setData(subsetFont, subsetFont.length);
-                    } else if (desc.getFontType() == FontType.TYPE1) {
-                        PFBParser parser = new PFBParser();
-                        PFBData pfb = parser.parsePFB(in);
-                        embeddedFont = new PDFT1Stream(obj);
-                        ((PDFT1Stream)embeddedFont).setData(pfb);
-                    } else {
-                        byte[] file = StreamUtilities.toByteArray(in, 128000);
-                        embeddedFont = new PDFTTFStream(obj, file.length);
-                        ((PDFTTFStream)embeddedFont).setData(file, file.length);
-                    }
-                    embeddedFont.addFilter("flate");
-                    if (isEncryptionActive()) {
-                        this.encryption.applyFilter(embeddedFont);
-                    } else {
-                        embeddedFont.addFilter("ascii-85");
-                    }
-                    
-                    return embeddedFont;
-                } finally {
-                    in.close();
-                }
-            }
-        } catch (IOException ioe) {
-            //log.error("Failed to embed font [" + obj + "] "
-            //                       + fontName + ": " + ioe.getMessage());
-            return (PDFStream) null;
-        }
-    }
-
-/*
-    public PDFStream getFontFile(int i) {
-        PDFStream embeddedFont = null;
-
-
-        return (PDFStream)embeddedFont;
-    }
-
-
-    public PDFStream getFontFile(int i) {
-    }
-
-*/
-    
-
-    /**
-     * make an Array object (ex. Widths array for a font)
-     *
-     * @param values the int array values
-     * @return the PDF Array with the int values
-     */
-    public PDFArray makeArray(int[] values) {
-        PDFArray array = new PDFArray(++this.objectcount, values);
-        this.objects.add(array);
-        return array;
-    }
-
-    /**
-     * make an ExtGState for extra graphics options
-     * This tries to find a GState that will setup the correct values
-     * for the current context. If there is no suitable GState it will
-     * create a new one.
-     *
-     * @param settings the settings required by the caller
-     * @param current the current GState of the current PDF context
-     * @return a PDF GState, either an existing GState or a new one
-     */
-    public PDFGState makeGState(Map settings, PDFGState current) {
-
-        // try to locate a gstate that has all the settings
-        // or will inherit from the current gstate
-        // compare "DEFAULT + settings" with "current + each gstate"
-
-        PDFGState wanted = new PDFGState(0);
-        wanted.addValues(PDFGState.DEFAULT);
-        wanted.addValues(settings);
-
-        PDFGState poss;
-        for (Iterator iter = gstates.iterator(); iter.hasNext();) {
-            PDFGState avail = (PDFGState)iter.next();
-            poss = new PDFGState(0);
-            poss.addValues(current);
-            poss.addValues(avail);
-            if (poss.equals(wanted)) {
-                return avail;
-            }
-        }
-
-        PDFGState gstate = new PDFGState(++this.objectcount);
-        gstate.addValues(settings);
-        this.objects.add(gstate);
-        gstates.add(gstate);
-        return gstate;
-    }
 
     /**
      * Get an image from the image map.
@@ -1507,9 +727,9 @@ public class PDFDocument extends AbstractLogEnabled {
         // setup image
         img.setup(this);
         // create a new XObject
-        xObject = new PDFXObject(++this.objectcount, ++this.xObjectCount,
+        xObject = new PDFXObject(++this.xObjectCount,
                                  img);
-        this.objects.add(xObject);
+        registerObject(xObject);
         this.resources.addXObject(xObject);
         if (res != null) {
             res.getPDFResources().addXObject(xObject);
@@ -1533,9 +753,9 @@ public class PDFDocument extends AbstractLogEnabled {
     public PDFFormXObject addFormXObject(PDFResourceContext res, PDFStream cont,
                                          PDFResources formres, String key) {
         PDFFormXObject xObject;
-        xObject = new PDFFormXObject(++this.objectcount, ++this.xObjectCount,
+        xObject = new PDFFormXObject(++this.xObjectCount,
                                  cont, formres.referencePDF());
-        this.objects.add(xObject);
+        registerObject(xObject);
         this.resources.addXObject(xObject);
         if (res != null) {
             res.getPDFResources().addXObject(xObject);
@@ -1543,270 +763,6 @@ public class PDFDocument extends AbstractLogEnabled {
         return xObject;
     }
 
-    /**
-     * make a /Page object
-     *
-     * @param resources resources object to use
-     * @param pagewidth width of the page in points
-     * @param pageheight height of the page in points
-     *
-     * @return the created /Page object
-     */
-    public PDFPage makePage(PDFResources resources,
-                            int pagewidth, int pageheight) {
-
-        /*
-         * create a PDFPage with the next object number, the given
-         * resources, contents and dimensions
-         */
-        PDFPage page = new PDFPage(this, ++this.objectcount, resources,
-                                   pagewidth, pageheight);
-
-        /* add it to the list of objects */
-        pages.addPage(page);
-        return page;
-    }
-
-    /**
-     * Add a completed page to the PDF document.
-     * The page is added to the object list.
-     *
-     * @param page the page to add
-     */
-    public void addPage(PDFPage page) {
-        /* add it to the list of objects */
-        this.objects.add(page);
-    }
-
-    private PDFLink findLink(PDFLink compare) {
-        return (PDFLink)findPDFObject(links, compare);
-    }
-
-    private PDFFileSpec findFileSpec(PDFFileSpec compare) {
-        return (PDFFileSpec)findPDFObject(filespecs, compare);
-    }
-
-    private PDFGoToRemote findGoToRemote(PDFGoToRemote compare) {
-        return (PDFGoToRemote)findPDFObject(gotoremotes, compare);
-    }
-
-    private PDFGoTo findGoTo(PDFGoTo compare) {
-        return (PDFGoTo)findPDFObject(gotos, compare);
-    }
-
-    /**
-     * make a link object
-     *
-     * @param rect   the clickable rectangle
-     * @param destination  the destination file
-     * @param linkType the link type
-     * @param yoffset the yoffset on the page for an internal link
-     * @return the PDFLink object created
-     */
-    public PDFLink makeLink(Rectangle2D rect, String destination,
-                            int linkType, float yoffset) {
-
-        //PDFLink linkObject;
-        int index;
-
-        PDFLink link = new PDFLink(++this.objectcount, rect);
-
-        if (linkType == PDFLink.EXTERNAL) {
-            // check destination
-            if (destination.startsWith("http://")) {
-                PDFUri uri = new PDFUri(destination);
-                link.setAction(uri);
-            } else if (destination.endsWith(".pdf")) {    // FileSpec
-                PDFGoToRemote remote = getGoToPDFAction(destination, null, -1);
-                link.setAction(remote);
-            } else if ((index = destination.indexOf(".pdf#page=")) > 0) {
-                //String file = destination.substring(0, index + 4);
-                int page = Integer.parseInt(destination.substring(index + 10));
-                PDFGoToRemote remote = getGoToPDFAction(destination, null, page);
-                link.setAction(remote);
-            } else if ((index = destination.indexOf(".pdf#dest=")) > 0) {
-                //String file = destination.substring(0, index + 4);
-                String dest = destination.substring(index + 10);
-                PDFGoToRemote remote = getGoToPDFAction(destination, dest, -1);
-                link.setAction(remote);
-            } else {                               // URI
-                PDFUri uri = new PDFUri(destination);
-                link.setAction(uri);
-            }
-        } else {
-            // linkType is internal
-            String goToReference = getGoToReference(destination, yoffset);
-            PDFInternalLink internalLink = new PDFInternalLink(goToReference);
-            link.setAction(internalLink);
-        }
-
-        PDFLink oldlink = findLink(link);
-        if (oldlink == null) {
-            links.add(link);
-            this.objects.add(link);
-        } else {
-            this.objectcount--;
-            link = oldlink;
-        }
-
-        return link;
-    }
-
-    /**
-     * Create and return a goto pdf document action.
-     * This creates a pdf files spec and pdf goto remote action.
-     * It also checks available pdf objects so it will not create an
-     * object if it already exists.
-     *
-     * @param file the pdf file name
-     * @param dest the remote name destination, may be null
-     * @param page the remote page number, -1 means not specified
-     * @return the pdf goto remote object
-     */
-    private PDFGoToRemote getGoToPDFAction(String file, String dest, int page) {
-        PDFFileSpec fileSpec = new PDFFileSpec(++this.objectcount, file);
-        PDFFileSpec oldspec = findFileSpec(fileSpec);
-        if (oldspec == null) {
-            filespecs.add(fileSpec);
-            this.objects.add(fileSpec);
-        } else {
-            this.objectcount--;
-            fileSpec = oldspec;
-        }
-        PDFGoToRemote remote;
-
-        if (dest == null && page == -1) {
-            remote = new PDFGoToRemote(++this.objectcount, fileSpec);
-        } else if (dest != null) {
-            remote = new PDFGoToRemote(++this.objectcount, fileSpec, dest);
-        } else {
-            remote = new PDFGoToRemote(++this.objectcount, fileSpec, page);
-        }
-        PDFGoToRemote oldremote = findGoToRemote(remote);
-        if (oldremote == null) {
-            gotoremotes.add(remote);
-            this.objects.add(remote);
-        } else {
-            this.objectcount--;
-            remote = oldremote;
-        }
-        return remote;
-    }
-
-    private String getGoToReference(String destination, float yoffset) {
-        String goToReference = null;
-        PDFGoTo gt = new PDFGoTo(++this.objectcount, destination);
-        gt.setYPosition(yoffset);
-        PDFGoTo oldgt = findGoTo(gt);
-        if (oldgt == null) {
-            gotos.add(gt);
-            addTrailerObject(gt);
-        } else {
-            this.objectcount--;
-            gt = oldgt;
-        }
-
-        goToReference = gt.referencePDF();
-        return goToReference;
-    }
-
-    /**
-     * Add trailer object.
-     * Adds an object to the list of trailer objects.
-     *
-     * @param object the PDF object to add
-     */
-    public void addTrailerObject(PDFObject object) {
-        this.trailerObjects.add(object);
-    }
-
-    /**
-     * Make an internal link.
-     *
-     * @param rect the hotspot position in absolute coordinates
-     * @param page the target page reference value
-     * @param dest the position destination
-     * @return the new PDF link object
-     */
-    public PDFLink makeLink(Rectangle2D rect, String page, String dest) {
-        PDFLink link = new PDFLink(++this.objectcount, rect);
-        this.objects.add(link);
-
-        PDFGoTo gt = new PDFGoTo(++this.objectcount, page);
-        gt.setDestination(dest);
-        addTrailerObject(gt);
-        PDFInternalLink internalLink = new PDFInternalLink(gt.referencePDF());
-        link.setAction(internalLink);
-
-        return link;
-    }
-
-    /**
-     * Ensure there is room in the locations xref for the number of
-     * objects that have been created.
-     */
-    private void prepareLocations() {
-        while (location.size() < objectcount) {
-            location.add(LOCATION_PLACEHOLDER);
-        }
-    }
-
-    /**
-     * Make a stream object
-     *
-     * @param type the type of stream to be created
-     * @param add if true then the stream will be added immediately
-     * @return the stream object created
-     */
-    public PDFStream makeStream(String type, boolean add) {
-
-        // create a PDFStream with the next object number 
-        // and add it to the list of objects
-        PDFStream obj = new PDFStream(++this.objectcount);
-        obj.addDefaultFilters(filterMap, type);
-        if (isEncryptionActive()) {
-            this.encryption.applyFilter(obj);
-        }
-
-        if (add) {
-            this.objects.add(obj);
-        }
-        return obj;
-    }
-
-    /**
-     * add a stream object
-     *
-     * @param obj the PDF Stream to add to this document
-     */
-    public void addStream(PDFStream obj) {
-        this.objects.add(obj);
-    }
-
-    /**
-     * make an annotation list object
-     *
-     * @return the annotation list object created
-     */
-    public PDFAnnotList makeAnnotList() {
-
-        /*
-         * create a PDFAnnotList with the next object number and add it
-         * to the list of objects
-         */
-        PDFAnnotList obj = new PDFAnnotList(++this.objectcount);
-        return obj;
-    }
-
-    /**
-     * Add an annotation list object to the pdf document
-     *
-     * @param obj the annotation list to add
-     */
-    public void addAnnotList(PDFAnnotList obj) {
-        this.objects.add(obj);
-    }
-
     /**
      * Get the root Outlines object. This method does not write
      * the outline to the PDF document, it simply creates a
@@ -1819,34 +775,13 @@ public class PDFDocument extends AbstractLogEnabled {
             return outlineRoot;
         }
 
-        outlineRoot = new PDFOutline(++this.objectcount, null, null);
+        outlineRoot = new PDFOutline(null, null);
+        assignObjectNumber(outlineRoot);
         addTrailerObject(outlineRoot);
         root.setRootOutline(outlineRoot);
         return outlineRoot;
     }
 
-    /**
-     * Make an outline object and add it to the given outline
-     *
-     * @param parent parent PDFOutline object which may be null
-     * @param label the title for the new outline object
-     * @param destination the reference string for the action to go to
-     * @param yoffset the yoffset on the destination page
-     * @return the new PDF outline object
-     */
-    public PDFOutline makeOutline(PDFOutline parent, String label,
-                                  String destination, float yoffset) {
-        String goToRef = getGoToReference(destination, yoffset);
-
-        PDFOutline obj = new PDFOutline(++this.objectcount, label, goToRef);
-
-        if (parent != null) {
-            parent.addOutline(obj);
-        }
-        this.objects.add(obj);
-        return obj;
-    }
-
     /**
      * get the /Resources object for the document
      *
@@ -1856,6 +791,17 @@ public class PDFDocument extends AbstractLogEnabled {
         return this.resources;
     }
 
+    /**
+     * Ensure there is room in the locations xref for the number of
+     * objects that have been created.
+     */
+    private void setLocation(int objidx, int position) {
+        while (location.size() <= objidx) {
+            location.add(LOCATION_PLACEHOLDER);
+        }
+        location.set(objidx, new Integer(position));
+    }
+
     /**
      * write the entire document out
      *
@@ -1863,19 +809,17 @@ public class PDFDocument extends AbstractLogEnabled {
      * @throws IOException if there is an exception writing to the output stream
      */
     public void output(OutputStream stream) throws IOException {
-
-        prepareLocations();
-
-        for (int count = 0; count < this.objects.size(); count++) {
-            /* retrieve the object with the current number */
-            PDFObject object = (PDFObject)this.objects.get(count);
-
+        //Write out objects until the list is empty. This approach (used with a
+        //LinkedList) allows for output() methods to create and register objects
+        //on the fly even during serialization.
+        while (this.objects.size() > 0) {
+            /* Retrieve first */
+            PDFObject object = (PDFObject)this.objects.remove(0);
             /*
              * add the position of this object to the list of object
              * locations
              */
-            location.set(object.getNumber() - 1,
-                         new Integer(this.position));
+            setLocation(object.getObjectNumber() - 1, this.position);
 
             /*
              * output the object and increment the character position
@@ -1884,7 +828,8 @@ public class PDFDocument extends AbstractLogEnabled {
             this.position += object.output(stream);
         }
 
-        this.objects.clear();
+        //Clear all objects written to the file
+        //this.objects.clear();
     }
 
     /**
@@ -1924,7 +869,7 @@ public class PDFDocument extends AbstractLogEnabled {
         output(stream);
         for (int count = 0; count < trailerObjects.size(); count++) {
             PDFObject o = (PDFObject) trailerObjects.get(count);
-            this.location.set(o.getNumber() - 1,
+            this.location.set(o.getObjectNumber() - 1,
                               new Integer(this.position));
             this.position += o.output(stream);
         }
@@ -1941,16 +886,14 @@ public class PDFDocument extends AbstractLogEnabled {
         /* construct the trailer */
         String pdf = "trailer\n" + "<<\n"
                      + "/Size " + (this.objectcount + 1) + "\n"
-                     + "/Root " + this.root.number + " "
-                     + this.root.generation + " R\n" 
-                     + "/Info " + this.info.number + " " 
-                     + this.info.generation + " R\n" 
+                     + "/Root " + this.root.referencePDF() + "\n" 
+                     + "/Info " + this.info.referencePDF() + "\n" 
                      + encryptEntry
                      + ">>\n" + "startxref\n" + this.xref
                      + "\n" + "%%EOF\n";
 
         /* write the trailer */
-        stream.write(pdf.getBytes());
+        stream.write(encode(pdf));
     }
 
     /**
@@ -1965,9 +908,10 @@ public class PDFDocument extends AbstractLogEnabled {
         this.xref = this.position;
 
         /* construct initial part of xref */
-        StringBuffer pdf = new StringBuffer("xref\n0 "
-                                            + (this.objectcount + 1)
-                                            + "\n0000000000 65535 f \n");
+        StringBuffer pdf = new StringBuffer(128);
+        pdf.append("xref\n0 "
+            + (this.objectcount + 1)
+            + "\n0000000000 65535 f \n");
 
         for (int count = 0; count < this.location.size(); count++) {
             String x = this.location.get(count).toString();
@@ -1981,7 +925,7 @@ public class PDFDocument extends AbstractLogEnabled {
         }
 
         /* write the xref table and return the character length */
-        byte[] pdfBytes = pdf.toString().getBytes();
+        byte[] pdfBytes = encode(pdf.toString());
         stream.write(pdfBytes);
         return pdfBytes.length;
     }