Added support for PDF object streams.
When accessibility is enabled and PDF version 1.5 selected, the structure tree will be stored in object streams in order to reduce the size of the final PDF.
This can lead to file reductions by up to 75%


git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_PDF_ObjectStreams@1303431 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/fop/pdf/AbstractPDFStream.java b/src/java/org/apache/fop/pdf/AbstractPDFStream.java
index 1b25c11..0181728 100644
--- a/src/java/org/apache/fop/pdf/AbstractPDFStream.java
+++ b/src/java/org/apache/fop/pdf/AbstractPDFStream.java
@@ -29,16 +29,48 @@
 /**
  * This is an abstract base class for PDF streams.
  */
-public abstract class AbstractPDFStream extends PDFDictionary {
+public abstract class AbstractPDFStream extends PDFObject {
+
+    private final PDFDictionary dictionary;
 
     /** The filters that should be applied */
     private PDFFilterList filters;
 
+    private final boolean encodeOnTheFly;
+
+    protected AbstractPDFStream() {
+        this(true);
+    }
+
+    protected AbstractPDFStream(PDFDictionary dictionary) {
+        this(dictionary, true);
+    }
+
+    protected AbstractPDFStream(boolean encodeOnTheFly) {
+        this(new PDFDictionary(), encodeOnTheFly);
+    }
+
+    protected AbstractPDFStream(PDFDictionary dictionary, boolean encodeOnTheFly) {
+        this.dictionary = dictionary;
+        this.encodeOnTheFly = encodeOnTheFly;
+    }
+
+    protected final PDFDictionary getDictionary() {
+        return dictionary;
+    }
+
+    protected Object get(String key) {
+        return dictionary.get(key);
+    }
+
     /**
-     * Constructor for AbstractPDFStream.
+     * Puts the given object in the dictionary associated to this stream.
+     *
+     * @param key the key in the dictionary
+     * @param value the value to store
      */
-    public AbstractPDFStream() {
-        super();
+    public void put(String key, Object value) {
+        dictionary.put(key, value);
     }
 
     /**
@@ -180,17 +212,16 @@
      * {@inheritDoc}
      */
     @Override
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         setupFilterList();
 
         CountingOutputStream cout = new CountingOutputStream(stream);
         StringBuilder textBuffer = new StringBuilder(64);
-        textBuffer.append(getObjectID());
 
         StreamCache encodedStream = null;
         PDFNumber refLength = null;
         final Object lengthEntry;
-        if (isEncodingOnTheFly()) {
+        if (encodeOnTheFly) {
             refLength = new PDFNumber();
             getDocumentSafely().registerObject(refLength);
             lengthEntry = refLength;
@@ -200,7 +231,7 @@
         }
 
         populateStreamDict(lengthEntry);
-        writeDictionary(cout, textBuffer);
+        dictionary.writeDictionary(cout, textBuffer);
 
         //Send encoded stream to target OutputStream
         PDFDocument.flushTextBuffer(textBuffer, cout);
@@ -211,18 +242,14 @@
             encodedStream.clear(); //Encoded stream can now be discarded
         }
 
-        textBuffer.append("\nendobj\n");
         PDFDocument.flushTextBuffer(textBuffer, cout);
         return cout.getCount();
     }
 
-    /**
-     * Indicates whether encoding may happen without buffering the encoded data. If this method
-     * returns true, the /Length entry will be an indirect object, a direct object otherwise.
-     * @return true if encoding should happen "on the fly"
-     */
-    protected boolean isEncodingOnTheFly() {
-        return getDocument().isEncodingOnTheFly();
+    @Override
+    public void setDocument(PDFDocument doc) {
+        dictionary.setDocument(doc);
+        super.setDocument(doc);
     }
 
     /**
@@ -233,7 +260,7 @@
     protected void populateStreamDict(Object lengthEntry) {
         put("Length", lengthEntry);
         if (!getFilterList().isDisableAllFilters()) {
-            getFilterList().putFilterDictEntries(this);
+            getFilterList().putFilterDictEntries(dictionary);
         }
     }
 
diff --git a/src/java/org/apache/fop/pdf/CompressedObject.java b/src/java/org/apache/fop/pdf/CompressedObject.java
new file mode 100644
index 0000000..55d9c29
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/CompressedObject.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Represents a PDF object that may appear in an object stream. An object stream is a PDF
+ * stream whose content is a sequence of PDF objects. See Section 3.4.6 of the PDF 1.5
+ * Reference.
+ */
+interface CompressedObject {
+
+    /**
+     * Returns the object number of this indirect object. Note that a compressed object
+     * must have a generation number of 0.
+     *
+     * @return the object number.
+     */
+    int getObjectNumber();
+
+    /**
+     * Outputs this object's content into the given stream.
+     *
+     * @param outputStream a stream, likely to be provided by the containing object stream
+     * @return the number of bytes written to the stream
+     * @throws IOException
+     */
+    int output(OutputStream outputStream) throws IOException;
+
+}
diff --git a/src/java/org/apache/fop/pdf/ObjectStream.java b/src/java/org/apache/fop/pdf/ObjectStream.java
new file mode 100644
index 0000000..e9335bc
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/ObjectStream.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.pdf.xref.CompressedObjectReference;
+
+/**
+ * An object stream, as described in section 3.4.6 of the PDF 1.5 Reference.
+ */
+public class ObjectStream extends PDFStream {
+
+    private static final PDFName OBJ_STM = new PDFName("ObjStm");
+
+    private List<CompressedObject> objects = new ArrayList<CompressedObject>();
+
+    private int firstObjectOffset;
+
+    ObjectStream() {
+        super(false);
+    }
+
+    ObjectStream(ObjectStream previous) {
+        this();
+        put("Extends", previous);
+    }
+
+    CompressedObjectReference addObject(CompressedObject obj) {
+        if (obj == null) {
+            throw new NullPointerException("obj must not be null");
+        }
+        CompressedObjectReference reference = new CompressedObjectReference(obj.getObjectNumber(),
+                getObjectNumber(), objects.size());
+        objects.add(obj);
+        return reference;
+    }
+
+    @Override
+    protected void outputRawStreamData(OutputStream out) throws IOException {
+        int currentOffset = 0;
+        StringBuilder offsetsPart = new StringBuilder();
+        ByteArrayOutputStream streamContent = new ByteArrayOutputStream();
+        for (CompressedObject object : objects) {
+            offsetsPart.append(object.getObjectNumber())
+                    .append(' ')
+                    .append(currentOffset)
+                    .append('\n');
+            currentOffset += object.output(streamContent);
+        }
+        byte[] offsets = PDFDocument.encode(offsetsPart.toString());
+        firstObjectOffset = offsets.length;
+        out.write(offsets);
+        streamContent.writeTo(out);
+    }
+
+    @Override
+    protected void populateStreamDict(Object lengthEntry) {
+        put("Type", OBJ_STM);
+        put("N", objects.size());
+        put("First", firstObjectOffset);
+        super.populateStreamDict(lengthEntry);
+    }
+}
diff --git a/src/java/org/apache/fop/pdf/ObjectStreamManager.java b/src/java/org/apache/fop/pdf/ObjectStreamManager.java
new file mode 100644
index 0000000..723facd
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/ObjectStreamManager.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.pdf.xref.CompressedObjectReference;
+
+/**
+ * Manages a collection of object streams, creating new streams as necessary to keep the
+ * number of objects in each stream at the recommended value. Streams are related to each
+ * other through the use of the Extends entry in the stream dictionary.
+ */
+class ObjectStreamManager {
+
+    private static final int OBJECT_STREAM_CAPACITY = 100;
+
+    private final PDFDocument pdfDocument;
+
+    private final List<CompressedObjectReference> compressedObjectReferences;
+
+    private int numObjectsInStream;
+
+    private ObjectStream currentObjectStream;
+
+    ObjectStreamManager(PDFDocument pdfDocument) {
+        this.pdfDocument = pdfDocument;
+        createObjectStream();
+        compressedObjectReferences = new ArrayList<CompressedObjectReference>();
+    }
+
+    void add(CompressedObject compressedObject) {
+        if (numObjectsInStream++ == OBJECT_STREAM_CAPACITY) {
+            createObjectStream();
+            numObjectsInStream = 1;
+        }
+        compressedObjectReferences.add(currentObjectStream.addObject(compressedObject));
+    }
+
+    private void createObjectStream() {
+        currentObjectStream = currentObjectStream == null
+                ? new ObjectStream()
+                : new ObjectStream(currentObjectStream);
+        pdfDocument.assignObjectNumber(currentObjectStream);
+        pdfDocument.addTrailerObject(currentObjectStream);
+    }
+
+    List<CompressedObjectReference> getCompressedObjectReferences() {
+        return compressedObjectReferences;
+    }
+}
diff --git a/src/java/org/apache/fop/pdf/PDFAnnotList.java b/src/java/org/apache/fop/pdf/PDFAnnotList.java
index 0a87106..65b327e 100644
--- a/src/java/org/apache/fop/pdf/PDFAnnotList.java
+++ b/src/java/org/apache/fop/pdf/PDFAnnotList.java
@@ -58,22 +58,19 @@
      */
     public String toPDFString() {
         StringBuffer p = new StringBuffer(128);
-        p.append(getObjectID());
         p.append("[\n");
         for (int i = 0; i < getCount(); i++) {
             p.append(((PDFObject)links.get(i)).referencePDF());
             p.append("\n");
         }
-        p.append("]\nendobj\n");
+        p.append("]");
         return p.toString();
     }
 
     /*
      * example
-     * 20 0 obj
      * [
      * 19 0 R
      * ]
-     * endobj
      */
 }
diff --git a/src/java/org/apache/fop/pdf/PDFArray.java b/src/java/org/apache/fop/pdf/PDFArray.java
index 78eba3b..1318303 100644
--- a/src/java/org/apache/fop/pdf/PDFArray.java
+++ b/src/java/org/apache/fop/pdf/PDFArray.java
@@ -21,7 +21,6 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
-import java.util.Collection;
 import java.util.List;
 
 import org.apache.commons.io.output.CountingOutputStream;
@@ -48,7 +47,7 @@
      * Create a new, empty array object with no parent.
      */
     public PDFArray() {
-        this(null);
+        this((PDFObject) null);
     }
 
     /**
@@ -84,7 +83,7 @@
      * @param parent the array's parent if any
      * @param values the actual values wrapped by this object
      */
-    public PDFArray(PDFObject parent, Collection<Object> values) {
+    public PDFArray(PDFObject parent, List<?> values) {
         /* generic creation of PDF object */
         super(parent);
 
@@ -92,6 +91,15 @@
     }
 
     /**
+     * Creates an array object made of the given elements.
+     *
+     * @param elements the array content
+     */
+    public PDFArray(Object... elements) {
+        this(null, elements);
+    }
+
+    /**
      * Create the array object
      * @param parent the array's parent if any
      * @param values the actual array wrapped by this object
@@ -180,13 +188,9 @@
 
     /** {@inheritDoc} */
     @Override
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         CountingOutputStream cout = new CountingOutputStream(stream);
         StringBuilder textBuffer = new StringBuilder(64);
-        if (hasObjectNumber()) {
-            textBuffer.append(getObjectID());
-        }
-
         textBuffer.append('[');
         for (int i = 0; i < values.size(); i++) {
             if (i > 0) {
@@ -196,11 +200,6 @@
             formatObject(obj, cout, textBuffer);
         }
         textBuffer.append(']');
-
-        if (hasObjectNumber()) {
-            textBuffer.append("\nendobj\n");
-        }
-
         PDFDocument.flushTextBuffer(textBuffer, cout);
         return cout.getCount();
     }
diff --git a/src/java/org/apache/fop/pdf/PDFCIDFont.java b/src/java/org/apache/fop/pdf/PDFCIDFont.java
index 459fe25..46374d8 100644
--- a/src/java/org/apache/fop/pdf/PDFCIDFont.java
+++ b/src/java/org/apache/fop/pdf/PDFCIDFont.java
@@ -200,7 +200,6 @@
      */
     public String toPDFString() {
         StringBuffer p = new StringBuffer(128);
-        p.append(getObjectID());
         p.append("<< /Type /Font");
         p.append("\n/BaseFont /");
         p.append(this.basefont);
@@ -234,14 +233,13 @@
             p.append("\n/DW2 [");    // always two values, see p 211
             p.append(this.dw2[0]);
             p.append(this.dw2[1]);
-            p.append("] \n>>\nendobj\n");
+            p.append("]");
         }
         if (w2 != null) {
             p.append("\n/W2 ");
             p.append(w2.toPDFString());
-            p.append(" \n>>\nendobj\n");
         }
-        p.append(" \n>>\nendobj\n");
+        p.append("\n>>");
         return p.toString();
     }
 
diff --git a/src/java/org/apache/fop/pdf/PDFCMap.java b/src/java/org/apache/fop/pdf/PDFCMap.java
index 57d148f..34bc0f9 100644
--- a/src/java/org/apache/fop/pdf/PDFCMap.java
+++ b/src/java/org/apache/fop/pdf/PDFCMap.java
@@ -423,7 +423,7 @@
     }
 
     /** {@inheritDoc} */
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         CMapBuilder builder = createCMapBuilder(getBufferWriter());
         builder.writeCMap();
         return super.output(stream);
diff --git a/src/java/org/apache/fop/pdf/PDFDestination.java b/src/java/org/apache/fop/pdf/PDFDestination.java
index 400c4a4..21c6558 100644
--- a/src/java/org/apache/fop/pdf/PDFDestination.java
+++ b/src/java/org/apache/fop/pdf/PDFDestination.java
@@ -50,9 +50,8 @@
         this.idRef = idRef;
     }
 
-    /** {@inheritDoc} */
     @Override
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         CountingOutputStream cout = new CountingOutputStream(stream);
         StringBuilder textBuffer = new StringBuilder(64);
 
diff --git a/src/java/org/apache/fop/pdf/PDFDictionary.java b/src/java/org/apache/fop/pdf/PDFDictionary.java
index 5a67243..6bacd31 100644
--- a/src/java/org/apache/fop/pdf/PDFDictionary.java
+++ b/src/java/org/apache/fop/pdf/PDFDictionary.java
@@ -98,19 +98,10 @@
 
     /** {@inheritDoc} */
     @Override
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         CountingOutputStream cout = new CountingOutputStream(stream);
         StringBuilder textBuffer = new StringBuilder(64);
-        if (hasObjectNumber()) {
-            textBuffer.append(getObjectID());
-        }
-
         writeDictionary(cout, textBuffer);
-
-        if (hasObjectNumber()) {
-            textBuffer.append("\nendobj\n");
-        }
-
         PDFDocument.flushTextBuffer(textBuffer, cout);
         return cout.getCount();
     }
diff --git a/src/java/org/apache/fop/pdf/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java
index e9886fc..9850c60 100644
--- a/src/java/org/apache/fop/pdf/PDFDocument.java
+++ b/src/java/org/apache/fop/pdf/PDFDocument.java
@@ -26,6 +26,7 @@
 import java.io.UnsupportedEncodingException;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
 import java.util.HashMap;
@@ -37,6 +38,10 @@
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
+import org.apache.fop.pdf.xref.CrossReferenceStream;
+import org.apache.fop.pdf.xref.CrossReferenceTable;
+import org.apache.fop.pdf.xref.TrailerDictionary;
+
 /* image support modified from work of BoBoGi */
 /* font support based on work by Takayuki Takeuchi */
 
@@ -63,31 +68,28 @@
  */
 public class PDFDocument {
 
-    private static final Long LOCATION_PLACEHOLDER = new Long(0);
-
     /** the encoding to use when converting strings to PDF commands */
     public static final String ENCODING = "ISO-8859-1";
 
     /** the counter for object numbering */
-    protected int objectcount = 0;
+    protected int objectcount;
 
     /** the logger instance */
     private Log log = LogFactory.getLog("org.apache.fop.pdf");
 
     /** the current character position */
-    private long position = 0;
-
-    /** character position of xref table */
-    private long xref;
+    private long position;
 
     /** the character position of each object */
-    private List<Long> location = new ArrayList<Long>();
+    private List<Long> indirectObjectOffsets = new ArrayList<Long>();
+
+    private Collection<PDFStructElem> structureTreeElements;
 
     /** List of objects to write in the trailer */
-    private List trailerObjects = new ArrayList();
+    private List<PDFObject> trailerObjects = new ArrayList<PDFObject>();
 
     /** the objects themselves */
-    private List objects = new LinkedList();
+    private List<PDFObject> objects = new LinkedList<PDFObject>();
 
     /** Controls the PDF version of this document */
     private VersionController versionController;
@@ -99,7 +101,7 @@
     private PDFRoot root;
 
     /** The root outline object */
-    private PDFOutline outlineRoot = null;
+    private PDFOutline outlineRoot;
 
     /** The /Pages object (mark-fop@inomial.com) */
     private PDFPages pages;
@@ -118,66 +120,47 @@
         = new PDFDeviceColorSpace(PDFDeviceColorSpace.DEVICE_RGB);
 
     /** the counter for Pattern name numbering (e.g. 'Pattern1') */
-    private int patternCount = 0;
+    private int patternCount;
 
     /** the counter for Shading name numbering */
-    private int shadingCount = 0;
+    private int shadingCount;
 
     /** the counter for XObject numbering */
-    private int xObjectCount = 0;
+    private int xObjectCount;
 
-    /** the {@link PDFXObject}s map */
     /* TODO: Should be modified (works only for image subtype) */
-    private Map xObjectsMap = new HashMap();
+    private Map<String, PDFXObject> xObjectsMap = new HashMap<String, PDFXObject>();
 
-    /** The {@link PDFFont} map */
-    private Map fontMap = new HashMap();
+    private Map<String, PDFFont> fontMap = new HashMap<String, PDFFont>();
 
-    /** The {@link PDFFilter} map */
-    private Map filterMap = new HashMap();
+    private Map<String, List<String>> filterMap = new HashMap<String, List<String>>();
 
-    /** List of {@link PDFGState}s. */
-    private List gstates = new ArrayList();
+    private List<PDFGState> gstates = new ArrayList<PDFGState>();
 
-    /** List of {@link PDFFunction}s. */
-    private List functions = new ArrayList();
+    private List<PDFFunction> functions = new ArrayList<PDFFunction>();
 
-    /** List of {@link PDFShading}s. */
-    private List shadings = new ArrayList();
+    private List<PDFShading> shadings = new ArrayList<PDFShading>();
 
-    /** List of {@link PDFPattern}s. */
-    private List patterns = new ArrayList();
+    private List<PDFPattern> patterns = new ArrayList<PDFPattern>();
 
-    /** List of {@link PDFLink}s. */
-    private List links = new ArrayList();
+    private List<PDFLink> links = new ArrayList<PDFLink>();
 
-    /** List of {@link PDFDestination}s. */
-    private List destinations;
+    private List<PDFDestination> destinations;
 
-    /** List of {@link PDFFileSpec}s. */
-    private List filespecs = new ArrayList();
+    private List<PDFFileSpec> filespecs = new ArrayList<PDFFileSpec>();
 
-    /** List of {@link PDFGoToRemote}s. */
-    private List gotoremotes = new ArrayList();
+    private List<PDFGoToRemote> gotoremotes = new ArrayList<PDFGoToRemote>();
 
-    /** List of {@link PDFGoTo}s. */
-    private List gotos = new ArrayList();
+    private List<PDFGoTo> gotos = new ArrayList<PDFGoTo>();
 
-    /** List of {@link PDFLaunch}es. */
-    private List launches = new ArrayList();
-
-    /**
-     * The PDFDests object for the name dictionary.
-     * Note: This object is not a list.
-     */
-    private PDFDests dests;
+    private List<PDFLaunch> launches = new ArrayList<PDFLaunch>();
 
     private PDFFactory factory;
 
-    private boolean encodingOnTheFly = true;
-
     private FileIDGenerator fileIDGenerator;
 
+    private boolean accessibilityEnabled;
+
     /**
      * Creates an empty PDF document.
      *
@@ -266,17 +249,6 @@
     }
 
     /**
-     * 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 <code>true</code> if on-the-fly encoding is enabled
-     */
-    public boolean isEncodingOnTheFly() {
-        return this.encodingOnTheFly;
-    }
-
-    /**
      * Converts text to a byte array for writing to a PDF file.
      *
      * @param text text to convert/encode
@@ -336,7 +308,7 @@
      *
      * @param map the map of filter lists for each stream type
      */
-    public void setFilterMap(Map map) {
+    public void setFilterMap(Map<String, List<String>> map) {
         this.filterMap = map;
     }
 
@@ -345,7 +317,7 @@
      *
      * @return the map of filters being used
      */
-    public Map getFilterMap() {
+    public Map<String, List<String>> getFilterMap() {
         return this.filterMap;
     }
 
@@ -368,6 +340,37 @@
     }
 
     /**
+     * Creates and returns a StructTreeRoot object.
+     *
+     * @param parentTree the value of the ParenTree entry
+     * @return the structure tree root
+     */
+    public PDFStructTreeRoot makeStructTreeRoot(PDFParentTree parentTree) {
+        PDFStructTreeRoot structTreeRoot = new PDFStructTreeRoot(parentTree);
+        assignObjectNumber(structTreeRoot);
+        addTrailerObject(structTreeRoot);
+        root.setStructTreeRoot(structTreeRoot);
+        structureTreeElements = new ArrayList<PDFStructElem>();
+        return structTreeRoot;
+    }
+
+    /**
+     * Creates and returns a structure element.
+     *
+     * @param structureType the structure type of the new element (value for the
+     * S entry)
+     * @param parent the parent of the new structure element in the structure
+     * hierarchy
+     * @return a dictionary of type StructElem
+     */
+    public PDFStructElem makeStructureElement(PDFName structureType, PDFObject parent) {
+        PDFStructElem structElem = new PDFStructElem(parent, structureType);
+        assignObjectNumber(structElem);
+        structureTreeElements.add(structElem);
+        return structElem;
+    }
+
+    /**
      * Get the {@link PDFInfo} object for this document.
      *
      * @return the {@link PDFInfo} object
@@ -439,39 +442,39 @@
 
         //Add object to special lists where necessary
         if (obj instanceof PDFFunction) {
-            this.functions.add(obj);
+            this.functions.add((PDFFunction) obj);
         }
         if (obj instanceof PDFShading) {
             final String shadingName = "Sh" + (++this.shadingCount);
             ((PDFShading)obj).setName(shadingName);
-            this.shadings.add(obj);
+            this.shadings.add((PDFShading) obj);
         }
         if (obj instanceof PDFPattern) {
             final String patternName = "Pa" + (++this.patternCount);
             ((PDFPattern)obj).setName(patternName);
-            this.patterns.add(obj);
+            this.patterns.add((PDFPattern) obj);
         }
         if (obj instanceof PDFFont) {
             final PDFFont font = (PDFFont)obj;
             this.fontMap.put(font.getName(), font);
         }
         if (obj instanceof PDFGState) {
-            this.gstates.add(obj);
+            this.gstates.add((PDFGState) obj);
         }
         if (obj instanceof PDFPage) {
             this.pages.notifyKidRegistered((PDFPage)obj);
         }
         if (obj instanceof PDFLaunch) {
-            this.launches.add(obj);
+            this.launches.add((PDFLaunch) obj);
         }
         if (obj instanceof PDFLink) {
-            this.links.add(obj);
+            this.links.add((PDFLink) obj);
         }
         if (obj instanceof PDFFileSpec) {
-            this.filespecs.add(obj);
+            this.filespecs.add((PDFFileSpec) obj);
         }
         if (obj instanceof PDFGoToRemote) {
-            this.gotoremotes.add(obj);
+            this.gotoremotes.add((PDFGoToRemote) obj);
         }
     }
 
@@ -485,7 +488,7 @@
         this.trailerObjects.add(obj);
 
         if (obj instanceof PDFGoTo) {
-            this.gotos.add(obj);
+            this.gotos.add((PDFGoTo) obj);
         }
     }
 
@@ -537,9 +540,8 @@
         return this.encryption;
     }
 
-    private Object findPDFObject(List list, PDFObject compare) {
-        for (Iterator iter = list.iterator(); iter.hasNext();) {
-            PDFObject obj = (PDFObject) iter.next();
+    private Object findPDFObject(List<? extends PDFObject> list, PDFObject compare) {
+        for (PDFObject obj : list) {
             if (compare.contentEquals(obj)) {
                 return obj;
             }
@@ -589,7 +591,7 @@
      * @return PDFFont the requested font, null if it wasn't found
      */
     protected PDFFont findFont(String fontname) {
-        return (PDFFont)this.fontMap.get(fontname);
+        return this.fontMap.get(fontname);
     }
 
     /**
@@ -601,7 +603,7 @@
     protected PDFDestination findDestination(PDFDestination compare) {
         int index = getDestinationList().indexOf(compare);
         if (index >= 0) {
-            return (PDFDestination)getDestinationList().get(index);
+            return getDestinationList().get(index);
         } else {
             return null;
         }
@@ -666,9 +668,9 @@
      */
     protected PDFGState findGState(PDFGState wanted, PDFGState current) {
         PDFGState poss;
-        Iterator iter = this.gstates.iterator();
+        Iterator<PDFGState> iter = this.gstates.iterator();
         while (iter.hasNext()) {
-            PDFGState avail = (PDFGState)iter.next();
+            PDFGState avail = iter.next();
             poss = new PDFGState();
             poss.addValues(current);
             poss.addValues(avail);
@@ -712,7 +714,7 @@
      *
      * @return the map of fonts used in this document
      */
-    public Map getFontMap() {
+    public Map<String, PDFFont> getFontMap() {
         return this.fontMap;
     }
 
@@ -753,16 +755,7 @@
      * @return the PDFXObject for the key if found
      */
     public PDFXObject getXObject(String key) {
-        return (PDFXObject)this.xObjectsMap.get(key);
-    }
-
-    /**
-     * Gets the PDFDests object (which represents the /Dests entry).
-     *
-     * @return the PDFDests object (which represents the /Dests entry).
-     */
-    public PDFDests getDests() {
-        return this.dests;
+        return this.xObjectsMap.get(key);
     }
 
     /**
@@ -771,7 +764,7 @@
      */
     public void addDestination(PDFDestination destination) {
         if (this.destinations == null) {
-            this.destinations = new ArrayList();
+            this.destinations = new ArrayList<PDFDestination>();
         }
         this.destinations.add(destination);
     }
@@ -781,11 +774,11 @@
      *
      * @return the list of named destinations.
      */
-    public List getDestinationList() {
+    public List<PDFDestination> getDestinationList() {
         if (hasDestinations()) {
             return this.destinations;
         } else {
-            return Collections.EMPTY_LIST;
+            return Collections.emptyList();
         }
     }
 
@@ -900,17 +893,8 @@
         return this.resources;
     }
 
-    /**
-     * Ensure there is room in the locations xref for the number of
-     * objects that have been created.
-     * @param objidx    the object's index
-     * @param position  the position
-     */
-    private void setLocation(int objidx, long position) {
-        while (this.location.size() <= objidx) {
-            this.location.add(LOCATION_PLACEHOLDER);
-        }
-        this.location.set(objidx, position);
+    public void enableAccessibility(boolean enableAccessibility) {
+        this.accessibilityEnabled = enableAccessibility;
     }
 
     /**
@@ -924,23 +908,50 @@
         //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
-             */
-            setLocation(object.getObjectNumber() - 1, this.position);
-
-            /*
-             * output the object and increment the character position
-             * by the object's length
-             */
-            this.position += object.output(stream);
+            PDFObject object = this.objects.remove(0);
+            streamIndirectObject(object, stream);
         }
+    }
 
-        //Clear all objects written to the file
-        //this.objects.clear();
+    private void streamIndirectObject(PDFObject o, OutputStream stream) throws IOException {
+        recordObjectOffset(o);
+        this.position += outputIndirectObject(o, stream);
+    }
+
+    private void streamIndirectObjects(Collection<? extends PDFObject> objects, OutputStream stream)
+            throws IOException {
+        for (PDFObject o : objects) {
+            streamIndirectObject(o, stream);
+        }
+    }
+
+    private void recordObjectOffset(PDFObject object) {
+        int index = object.getObjectNumber() - 1;
+        while (indirectObjectOffsets.size() <= index) {
+            indirectObjectOffsets.add(null);
+        }
+        indirectObjectOffsets.set(index, position);
+    }
+
+    /**
+     * Outputs the given object, wrapped by obj/endobj, to the given stream.
+     *
+     * @param object an indirect object, as described in Section 3.2.9 of the PDF 1.5
+     * Reference.
+     * @param stream the stream to which the object must be output
+     * @throws IllegalArgumentException if the object is not an indirect object
+     */
+    public static int outputIndirectObject(PDFObject object, OutputStream stream)
+            throws IOException {
+        if (!object.hasObjectNumber()) {
+            throw new IllegalArgumentException("Not an indirect object");
+        }
+        byte[] obj = encode(object.getObjectID());
+        stream.write(obj);
+        int length = object.output(stream);
+        byte[] endobj = encode("\nendobj\n");
+        stream.write(endobj);
+        return obj.length + length + endobj.length;
     }
 
     /**
@@ -980,89 +991,102 @@
      * @throws IOException if there is an exception writing to the output stream
      */
     public void outputTrailer(OutputStream stream) throws IOException {
+        createDestinations();
+        output(stream);
+        outputTrailerObjectsAndXref(stream);
+    }
+
+    private void createDestinations() {
         if (hasDestinations()) {
             Collections.sort(this.destinations, new DestinationComparator());
-            this.dests = getFactory().makeDests(this.destinations);
+            PDFDests dests = getFactory().makeDests(this.destinations);
             if (this.root.getNames() == null) {
                 this.root.setNames(getFactory().makeNames());
             }
             this.root.getNames().setDests(dests);
         }
-        output(stream);
-        for (int count = 0; count < this.trailerObjects.size(); count++) {
-            PDFObject o = (PDFObject)this.trailerObjects.get(count);
-            setLocation(o.getObjectNumber() - 1, this.position);
-            this.position += o.output(stream);
-        }
-        /* output the xref table and increment the character position
-          by the table's length */
-        this.position += outputXref(stream);
-
-        /* construct the trailer */
-        StringBuffer pdf = new StringBuffer(128);
-        pdf.append("trailer\n<<\n/Size ")
-                .append(this.objectcount + 1)
-                .append("\n/Root ")
-                .append(this.root.referencePDF())
-                .append("\n/Info ")
-                .append(this.info.referencePDF())
-                .append('\n');
-
-        if (this.isEncryptionActive()) {
-            pdf.append(this.encryption.getTrailerEntry());
-        } else {
-            byte[] fileID = getFileIDGenerator().getOriginalFileID();
-            String fileIDAsString = PDFText.toHex(fileID);
-            pdf.append("/ID [" + fileIDAsString + " " + fileIDAsString + "]");
-        }
-
-        pdf.append("\n>>\nstartxref\n")
-                .append(this.xref)
-                .append("\n%%EOF\n");
-
-        /* write the trailer */
-        stream.write(encode(pdf.toString()));
     }
 
-    /**
-     * Write the xref table
-     *
-     * @param stream the OutputStream to write the xref table to
-     * @return the number of characters written
-     * @throws IOException in case of an error writing the result to
-     *          the parameter stream
-     */
-    private int outputXref(OutputStream stream) throws IOException {
+    private void outputTrailerObjectsAndXref(OutputStream stream) throws IOException {
+        TrailerOutputHelper trailerOutputHelper = mayCompressStructureTreeElements()
+                ? new CompressedTrailerOutputHelper()
+                : new UncompressedTrailerOutputHelper();
+        if (structureTreeElements != null) {
+            trailerOutputHelper.outputStructureTreeElements(stream);
+        }
+        streamIndirectObjects(trailerObjects, stream);
+        TrailerDictionary trailerDictionary = createTrailerDictionary();
+        long startxref = trailerOutputHelper.outputCrossReferenceObject(stream, trailerDictionary);
+        String trailer = "startxref\n" + startxref + "\n%%EOF\n";
+        stream.write(encode(trailer));
+    }
 
-        /* remember position of xref table */
-        this.xref = this.position;
+    private boolean mayCompressStructureTreeElements() {
+        return accessibilityEnabled
+                && versionController.getPDFVersion().compareTo(Version.V1_5) >= 0;
+    }
 
-        /* construct initial part of xref */
-        StringBuffer pdf = new StringBuffer(128);
-        pdf.append("xref\n0 ");
-        pdf.append(this.objectcount + 1);
-        pdf.append("\n0000000000 65535 f \n");
+    private TrailerDictionary createTrailerDictionary() {
+        FileIDGenerator gen = getFileIDGenerator();
+        TrailerDictionary trailerDictionary = new TrailerDictionary(this)
+                .setRoot(root)
+                .setInfo(info)
+                .setFileID(gen.getOriginalFileID(), gen.getUpdatedFileID());
+        if (isEncryptionActive()) {
+            trailerDictionary.setEncryption(encryption);
+        }
+        return trailerDictionary;
+    }
 
-        String s;
-        String loc;
-        for (int count = 0; count < this.location.size(); count++) {
-            final String padding = "0000000000";
-            s = this.location.get(count).toString();
-            if (s.length() > 10) {
-                throw new IOException("PDF file too large. PDF cannot grow beyond approx. 9.3GB.");
-            }
+    private interface TrailerOutputHelper {
 
-            /* contruct xref entry for object */
-            loc = padding.substring(s.length()) + s;
+        void outputStructureTreeElements(OutputStream stream) throws IOException;
 
-            /* append to xref table */
-            pdf = pdf.append(loc).append(" 00000 n \n");
+        /**
+         * @return the offset of the cross-reference object (the value of startxref)
+         */
+        long outputCrossReferenceObject(OutputStream stream, TrailerDictionary trailerDictionary)
+                throws IOException;
+    }
+
+    private class UncompressedTrailerOutputHelper implements TrailerOutputHelper {
+
+        public void outputStructureTreeElements(OutputStream stream)
+                throws IOException {
+            streamIndirectObjects(structureTreeElements, stream);
         }
 
-        /* write the xref table and return the character length */
-        byte[] pdfBytes = encode(pdf.toString());
-        stream.write(pdfBytes);
-        return pdfBytes.length;
+        public long outputCrossReferenceObject(OutputStream stream,
+                TrailerDictionary trailerDictionary) throws IOException {
+            new CrossReferenceTable(trailerDictionary, position,
+                    indirectObjectOffsets).output(stream);
+            return position;
+        }
+    }
+
+    private class CompressedTrailerOutputHelper implements TrailerOutputHelper {
+
+        private ObjectStreamManager structureTreeObjectStreams;
+
+        public void outputStructureTreeElements(OutputStream stream)
+                throws IOException {
+            assert structureTreeElements.size() > 0;
+            structureTreeObjectStreams = new ObjectStreamManager(PDFDocument.this);
+            for (PDFStructElem structElem : structureTreeElements) {
+                structureTreeObjectStreams.add(structElem);
+            }
+        }
+
+        public long outputCrossReferenceObject(OutputStream stream,
+                TrailerDictionary trailerDictionary) throws IOException {
+            // Outputting the object streams should not have created new indirect objects
+            assert objects.isEmpty();
+            new CrossReferenceStream(PDFDocument.this, ++objectcount, trailerDictionary, position,
+                    indirectObjectOffsets,
+                    structureTreeObjectStreams.getCompressedObjectReferences())
+                    .output(stream);
+            return position;
+        }
     }
 
     long getCurrentFileSize() {
diff --git a/src/java/org/apache/fop/pdf/PDFEncryption.java b/src/java/org/apache/fop/pdf/PDFEncryption.java
index 277cf0a..fcb56e5 100644
--- a/src/java/org/apache/fop/pdf/PDFEncryption.java
+++ b/src/java/org/apache/fop/pdf/PDFEncryption.java
@@ -40,8 +40,10 @@
     byte[] encrypt(byte[] data, PDFObject refObj);
 
     /**
-     * Returns the trailer entry for encryption.
-     * @return the trailer entry
+     * Returns the /Encrypt entry in the file trailer dictionary.
+     *
+     * @return the string "/Encrypt n g R\n" where n and g are the number and generation
+     * of the document's encryption dictionary
      */
     String getTrailerEntry();
 }
diff --git a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
index c9b9c58..2ef3b93 100644
--- a/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
+++ b/src/java/org/apache/fop/pdf/PDFEncryptionJCE.java
@@ -69,8 +69,9 @@
                     ? new Rev2Engine(encryptionSettings)
                     : new Rev3Engine(encryptionSettings);
             initializationEngine.run();
-            encryptionDictionary = createEncryptionDictionary(getObjectID(), permissions,
-                    initializationEngine.oValue, initializationEngine.uValue);
+            encryptionDictionary = createEncryptionDictionary(permissions,
+                    initializationEngine.oValue,
+                    initializationEngine.uValue);
         }
 
         private void determineEncryptionAlgorithm() {
@@ -91,18 +92,16 @@
                     && encryptionParams.isAllowPrintHq();
         }
 
-        private String createEncryptionDictionary(final String objectId, final int permissions,
-                final byte[] oValue, final byte[] uValue) {
-            return objectId
-                    + "<< /Filter /Standard\n"
+        private String createEncryptionDictionary(final int permissions, final byte[] oValue,
+                final byte[] uValue) {
+            return "<< /Filter /Standard\n"
                     + "/V " + version + "\n"
                     + "/R " + revision + "\n"
                     + "/Length " + encryptionLength + "\n"
                     + "/P "  + permissions + "\n"
                     + "/O " + PDFText.toHex(oValue) + "\n"
                     + "/U " + PDFText.toHex(uValue) + "\n"
-                    + ">>\n"
-                    + "endobj\n";
+                    + ">>";
         }
 
     }
@@ -488,14 +487,7 @@
 
     /** {@inheritDoc} */
     public String getTrailerEntry() {
-        PDFDocument doc = getDocumentSafely();
-        FileIDGenerator gen = doc.getFileIDGenerator();
-        return "/Encrypt " + getObjectNumber() + " "
-                    + getGeneration() + " R\n"
-                    + "/ID["
-                    + PDFText.toHex(gen.getOriginalFileID())
-                    + PDFText.toHex(gen.getUpdatedFileID())
-                    + "]\n";
+        return "/Encrypt " + getObjectNumber() + " " + getGeneration() + " R\n";
     }
 
     private static byte[] encryptWithKey(byte[] key, byte[] data) {
diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java
index a981dae..2c34703 100644
--- a/src/java/org/apache/fop/pdf/PDFFactory.java
+++ b/src/java/org/apache/fop/pdf/PDFFactory.java
@@ -913,35 +913,6 @@
     }
 
     /**
-     * Creates and returns a StructTreeRoot object. Used for accessibility.
-     * @param parentTree the value of the ParenTree entry
-     * @return structure Tree Root element
-     */
-    public PDFStructTreeRoot makeStructTreeRoot(PDFParentTree parentTree) {
-        PDFStructTreeRoot structTreeRoot = new PDFStructTreeRoot(parentTree);
-        getDocument().assignObjectNumber(structTreeRoot);
-        getDocument().addTrailerObject(structTreeRoot);
-        getDocument().getRoot().setStructTreeRoot(structTreeRoot);
-        return structTreeRoot;
-    }
-
-    /**
-     * Creates and returns a StructElem object.
-     *
-     * @param structureType the structure type of the new element (value for the
-     * S entry)
-     * @param parent the parent of the new structure element in the structure
-     * hierarchy
-     * @return the newly created element
-     */
-    public PDFStructElem makeStructureElement(PDFName structureType, PDFObject parent) {
-        PDFStructElem structElem = new PDFStructElem(parent, structureType);
-        getDocument().assignObjectNumber(structElem);
-        getDocument().addTrailerObject(structElem);
-        return structElem;
-    }
-
-    /**
      * Make a the head object of the name dictionary (the /Dests object).
      *
      * @param destinationList a list of PDFDestination instances
diff --git a/src/java/org/apache/fop/pdf/PDFFilterList.java b/src/java/org/apache/fop/pdf/PDFFilterList.java
index 3025b87..3f26f45 100644
--- a/src/java/org/apache/fop/pdf/PDFFilterList.java
+++ b/src/java/org/apache/fop/pdf/PDFFilterList.java
@@ -21,6 +21,7 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -47,7 +48,7 @@
     /** Key for the filter used for metadata */
     public static final String METADATA_FILTER = "metadata";
 
-    private List filters = new java.util.ArrayList();
+    private List<PDFFilter> filters = new java.util.ArrayList<PDFFilter>();
 
     private boolean ignoreASCIIFilters = false;
 
@@ -197,6 +198,10 @@
         }
     }
 
+    List<PDFFilter> getFilters() {
+        return Collections.unmodifiableList(filters);
+    }
+
     /**
      * Apply the filters to the data
      * in the order given and return the /Filter and /DecodeParms
@@ -206,7 +211,7 @@
      * @return a String representing the filter list
      */
     protected String buildFilterDictEntries() {
-        if (filters != null && filters.size() > 0) {
+        if (filters.size() > 0) {
             List names = new java.util.ArrayList();
             List parms = new java.util.ArrayList();
 
@@ -229,7 +234,7 @@
      * @param dict the PDFDictionary to set the entries on
      */
     protected void putFilterDictEntries(PDFDictionary dict) {
-        if (filters != null && filters.size() > 0) {
+        if (filters.size() > 0) {
             List names = new java.util.ArrayList();
             List parms = new java.util.ArrayList();
 
@@ -358,7 +363,7 @@
      */
     public OutputStream applyFilters(OutputStream stream) throws IOException {
         OutputStream out = stream;
-        if (filters != null && !isDisableAllFilters()) {
+        if (!isDisableAllFilters()) {
             for (int count = filters.size() - 1; count >= 0; count--) {
                 PDFFilter filter = (PDFFilter)filters.get(count);
                 out = filter.applyFilter(out);
diff --git a/src/java/org/apache/fop/pdf/PDFFont.java b/src/java/org/apache/fop/pdf/PDFFont.java
index 2808e5b..191fd22 100644
--- a/src/java/org/apache/fop/pdf/PDFFont.java
+++ b/src/java/org/apache/fop/pdf/PDFFont.java
@@ -174,7 +174,7 @@
     }
 
     /** {@inheritDoc} */
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         validate();
         return super.output(stream);
     }
diff --git a/src/java/org/apache/fop/pdf/PDFFormXObject.java b/src/java/org/apache/fop/pdf/PDFFormXObject.java
index 2ccc170..bc81a0a 100644
--- a/src/java/org/apache/fop/pdf/PDFFormXObject.java
+++ b/src/java/org/apache/fop/pdf/PDFFormXObject.java
@@ -44,7 +44,7 @@
      * @param resources the resource PDF reference
      */
     public PDFFormXObject(int xnumber, PDFStream contents, PDFReference resources) {
-        super();
+        super(contents.getDictionary());
         put("Name", new PDFName("Form" + xnumber));
         this.contents = contents;
 
@@ -160,7 +160,7 @@
     }
 
     /** {@inheritDoc} */
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         final int len = super.output(stream);
 
         //Now that the data has been written, it can be discarded.
diff --git a/src/java/org/apache/fop/pdf/PDFFunction.java b/src/java/org/apache/fop/pdf/PDFFunction.java
index 9e93ebe..4419810 100644
--- a/src/java/org/apache/fop/pdf/PDFFunction.java
+++ b/src/java/org/apache/fop/pdf/PDFFunction.java
@@ -380,8 +380,7 @@
         int numberOfFunctions = 0;
         int tempInt = 0;
         StringBuffer p = new StringBuffer(256);
-        p.append(getObjectID()
-                + "<< \n/FunctionType " + this.functionType + " \n");
+        p.append("<< \n/FunctionType " + this.functionType + " \n");
 
         // FunctionType 0
         if (this.functionType == 0) {
@@ -482,15 +481,14 @@
                     p.append("] \n");
                 }
             }
-            p.append(">> \n");
+            p.append(">>");
 
             // stream representing the function
             if (this.functionDataStream != null) {
-                p.append("stream\n" + this.functionDataStream
-                         + "\nendstream\n");
+                p.append("\nstream\n" + this.functionDataStream
+                         + "\nendstream");
             }
 
-            p.append("endobj\n");
             // end of if FunctionType 0
 
         } else if (this.functionType == 2) {
@@ -550,7 +548,7 @@
                      + PDFNumber.doubleOut(new Double(this.interpolationExponentN))
                      + " \n");
 
-            p.append(">> \nendobj\n");
+            p.append(">>");
 
         } else if (this.functionType
                    == 3) {                       // fix this up when my eyes uncross
@@ -643,10 +641,7 @@
                 }
 
             }
-            p.append("] \n");
-
-
-            p.append(">> \nendobj\n");
+            p.append("]\n>>");
         } else if (this.functionType
                    == 4) {                       // fix this up when my eyes uncross
             // DOMAIN
@@ -681,15 +676,14 @@
                          + " \n");
             }
 
-            p.append(">> \n");
+            p.append(">>");
 
             // stream representing the function
             if (this.functionDataStream != null) {
-                p.append("stream\n{ " + this.functionDataStream
-                         + " } \nendstream\n");
+                p.append("\nstream\n{ " + this.functionDataStream
+                         + " }\nendstream");
             }
 
-            p.append("endobj\n");
 
         }
 
diff --git a/src/java/org/apache/fop/pdf/PDFGState.java b/src/java/org/apache/fop/pdf/PDFGState.java
index fe57e39..7306218 100644
--- a/src/java/org/apache/fop/pdf/PDFGState.java
+++ b/src/java/org/apache/fop/pdf/PDFGState.java
@@ -149,12 +149,10 @@
      */
     public String toPDFString() {
         StringBuffer sb = new StringBuffer(64);
-        sb.append(getObjectID());
         sb.append("<<\n/Type /ExtGState\n");
         appendVal(sb, GSTATE_ALPHA_NONSTROKE);
         appendVal(sb, GSTATE_ALPHA_STROKE);
-
-        sb.append(">>\nendobj\n");
+        sb.append(">>");
         return sb.toString();
     }
 
diff --git a/src/java/org/apache/fop/pdf/PDFGoTo.java b/src/java/org/apache/fop/pdf/PDFGoTo.java
index 0626f0f..784aa4f 100644
--- a/src/java/org/apache/fop/pdf/PDFGoTo.java
+++ b/src/java/org/apache/fop/pdf/PDFGoTo.java
@@ -124,9 +124,7 @@
         } else {
             dest = "/D [" + this.pageReference + " " + destination + "]\n";
         }
-        return getObjectID()
-                    + "<< /Type /Action\n/S /GoTo\n" + dest
-                    + ">>\nendobj\n";
+        return "<< /Type /Action\n/S /GoTo\n" + dest + ">>";
     }
 
     /*
diff --git a/src/java/org/apache/fop/pdf/PDFGoToRemote.java b/src/java/org/apache/fop/pdf/PDFGoToRemote.java
index 93fbe47..2dccdaf 100644
--- a/src/java/org/apache/fop/pdf/PDFGoToRemote.java
+++ b/src/java/org/apache/fop/pdf/PDFGoToRemote.java
@@ -106,7 +106,6 @@
      */
     public String toPDFString() {
         StringBuffer sb = new StringBuffer(64);
-        sb.append(getObjectID());
         sb.append("<<\n/S /GoToR\n/F ");
         sb.append(pdfFileSpec.toString());
         sb.append("\n");
@@ -121,7 +120,7 @@
             sb.append("/NewWindow true");
         }
 
-        sb.append(" \n>>\nendobj\n");
+        sb.append("\n>>");
 
         return sb.toString();
     }
diff --git a/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java b/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java
index 87e8f54..b86ba29 100644
--- a/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java
+++ b/src/java/org/apache/fop/pdf/PDFICCBasedColorSpace.java
@@ -99,9 +99,7 @@
     @Override
     protected String toPDFString() {
         StringBuffer sb = new StringBuffer(64);
-        sb.append(getObjectID());
         sb.append("[/ICCBased ").append(getICCStream().referencePDF()).append("]");
-        sb.append("\nendobj\n");
         return sb.toString();
     }
 
diff --git a/src/java/org/apache/fop/pdf/PDFICCStream.java b/src/java/org/apache/fop/pdf/PDFICCStream.java
index 9002fcc..33b8130 100644
--- a/src/java/org/apache/fop/pdf/PDFICCStream.java
+++ b/src/java/org/apache/fop/pdf/PDFICCStream.java
@@ -64,7 +64,7 @@
      * {@inheritDoc}
      */
     @Override
-    protected int output(java.io.OutputStream stream)
+    public int output(java.io.OutputStream stream)
                 throws java.io.IOException {
         int length = super.output(stream);
         this.cp = null; //Free ICC stream when it's not used anymore
diff --git a/src/java/org/apache/fop/pdf/PDFImageXObject.java b/src/java/org/apache/fop/pdf/PDFImageXObject.java
index 9ec8f15..acab4c1 100644
--- a/src/java/org/apache/fop/pdf/PDFImageXObject.java
+++ b/src/java/org/apache/fop/pdf/PDFImageXObject.java
@@ -61,7 +61,7 @@
      * @throws IOException if there is an error writing the data
      * @return the length of the data written
      */
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         int length = super.output(stream);
 
         // let it gc
@@ -137,7 +137,7 @@
             put("SMask", ref);
         }
         //Important: do this at the end so previous values can be overwritten.
-        pdfimage.populateXObjectDictionary(this);
+        pdfimage.populateXObjectDictionary(getDictionary());
     }
 
     /** {@inheritDoc} */
diff --git a/src/java/org/apache/fop/pdf/PDFInfo.java b/src/java/org/apache/fop/pdf/PDFInfo.java
index 3f3fb3b..d457c28 100644
--- a/src/java/org/apache/fop/pdf/PDFInfo.java
+++ b/src/java/org/apache/fop/pdf/PDFInfo.java
@@ -169,7 +169,6 @@
         PDFProfile profile = getDocumentSafely().getProfile();
         ByteArrayOutputStream bout = new ByteArrayOutputStream(128);
         try {
-            bout.write(encode(getObjectID()));
             bout.write(encode("<<\n"));
             if (title != null && title.length() > 0) {
                 bout.write(encode("/Title "));
@@ -229,7 +228,7 @@
                 bout.write(encode("/Trapped /False\n"));
             }
 
-            bout.write(encode(">>\nendobj\n"));
+            bout.write(encode(">>"));
         } catch (IOException ioe) {
             log.error("Ignored I/O exception", ioe);
         }
diff --git a/src/java/org/apache/fop/pdf/PDFLaunch.java b/src/java/org/apache/fop/pdf/PDFLaunch.java
index e62da52..7d80ddb 100644
--- a/src/java/org/apache/fop/pdf/PDFLaunch.java
+++ b/src/java/org/apache/fop/pdf/PDFLaunch.java
@@ -54,10 +54,9 @@
     /** {@inheritDoc} */
     public String toPDFString() {
         StringBuffer sb = new StringBuffer(64);
-        sb.append(getObjectID());
         sb.append("<<\n/S /Launch\n/F ");
         sb.append(externalFileSpec.toString());
-        sb.append(" \n>>\nendobj\n");
+        sb.append("\n>>");
 
         return sb.toString();
     }
diff --git a/src/java/org/apache/fop/pdf/PDFLink.java b/src/java/org/apache/fop/pdf/PDFLink.java
index 9349326..ffec6f0 100644
--- a/src/java/org/apache/fop/pdf/PDFLink.java
+++ b/src/java/org/apache/fop/pdf/PDFLink.java
@@ -92,15 +92,14 @@
             f |= 1 << (5 - 1); //NoRotate, bit 5
             fFlag = "/F " + f;
         }
-        String s = getObjectID()
-                   + "<< /Type /Annot\n" + "/Subtype /Link\n" + "/Rect [ "
+        String s = "<< /Type /Annot\n" + "/Subtype /Link\n" + "/Rect [ "
                    + (ulx) + " " + (uly) + " "
                    + (brx) + " " + (bry) + " ]\n" + "/C [ "
                    + this.color + " ]\n" + "/Border [ 0 0 0 ]\n" + "/A "
                    + this.action.getAction() + "\n" + "/H /I\n"
                    + (this.structParent != null
                            ? "/StructParent " + this.structParent.toString() + "\n" : "")
-                   + fFlag + "\n>>\nendobj\n";
+                   + fFlag + "\n>>";
         return s;
     }
 
diff --git a/src/java/org/apache/fop/pdf/PDFMetadata.java b/src/java/org/apache/fop/pdf/PDFMetadata.java
index b9612d1..9b1b816 100644
--- a/src/java/org/apache/fop/pdf/PDFMetadata.java
+++ b/src/java/org/apache/fop/pdf/PDFMetadata.java
@@ -79,7 +79,7 @@
      * byte arrays around so much
      * {@inheritDoc}
      */
-    protected int output(java.io.OutputStream stream)
+    public int output(java.io.OutputStream stream)
                 throws java.io.IOException {
         int length = super.output(stream);
         this.xmpMetadata = null; //Release DOM when it's not used anymore
diff --git a/src/java/org/apache/fop/pdf/PDFName.java b/src/java/org/apache/fop/pdf/PDFName.java
index 7fa6842..23294cc 100644
--- a/src/java/org/apache/fop/pdf/PDFName.java
+++ b/src/java/org/apache/fop/pdf/PDFName.java
@@ -108,22 +108,11 @@
         return name.hashCode();
     }
 
-
-    /** {@inheritDoc} */
     @Override
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         CountingOutputStream cout = new CountingOutputStream(stream);
         StringBuilder textBuffer = new StringBuilder(64);
-        if (hasObjectNumber()) {
-            textBuffer.append(getObjectID());
-        }
-
         textBuffer.append(toString());
-
-        if (hasObjectNumber()) {
-            textBuffer.append("\nendobj\n");
-        }
-
         PDFDocument.flushTextBuffer(textBuffer, cout);
         return cout.getCount();
     }
diff --git a/src/java/org/apache/fop/pdf/PDFNumber.java b/src/java/org/apache/fop/pdf/PDFNumber.java
index 5bc648c..9524230 100644
--- a/src/java/org/apache/fop/pdf/PDFNumber.java
+++ b/src/java/org/apache/fop/pdf/PDFNumber.java
@@ -85,13 +85,7 @@
                 "The number of this PDFNumber must not be empty");
         }
         StringBuffer sb = new StringBuffer(64);
-        if (hasObjectNumber()) {
-            sb.append(getObjectID());
-        }
         sb.append(doubleOut(getNumber().doubleValue(), 10));
-        if (hasObjectNumber()) {
-            sb.append("\nendobj\n");
-        }
         return sb.toString();
     }
 
diff --git a/src/java/org/apache/fop/pdf/PDFNumsArray.java b/src/java/org/apache/fop/pdf/PDFNumsArray.java
index ecd3016..e9e1855 100644
--- a/src/java/org/apache/fop/pdf/PDFNumsArray.java
+++ b/src/java/org/apache/fop/pdf/PDFNumsArray.java
@@ -88,13 +88,9 @@
 
     /** {@inheritDoc} */
     @Override
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         CountingOutputStream cout = new CountingOutputStream(stream);
         StringBuilder textBuffer = new StringBuilder(64);
-        if (hasObjectNumber()) {
-            textBuffer.append(getObjectID());
-        }
-
         textBuffer.append('[');
         boolean first = true;
         for (Map.Entry<Integer, Object> entry : this.map.entrySet()) {
@@ -107,11 +103,6 @@
             formatObject(entry.getValue(), cout, textBuffer);
         }
         textBuffer.append(']');
-
-        if (hasObjectNumber()) {
-            textBuffer.append("\nendobj\n");
-        }
-
         PDFDocument.flushTextBuffer(textBuffer, cout);
         return cout.getCount();
     }
diff --git a/src/java/org/apache/fop/pdf/PDFObject.java b/src/java/org/apache/fop/pdf/PDFObject.java
index 5abb5a1..1b9c4ee 100644
--- a/src/java/org/apache/fop/pdf/PDFObject.java
+++ b/src/java/org/apache/fop/pdf/PDFObject.java
@@ -204,7 +204,7 @@
      * @throws IOException if there is an error writing to the stream
      * @return the number of bytes written
      */
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         byte[] pdf = this.toPDF();
         stream.write(pdf);
         return pdf.length;
diff --git a/src/java/org/apache/fop/pdf/PDFOutline.java b/src/java/org/apache/fop/pdf/PDFOutline.java
index 66116c6..61c5625 100644
--- a/src/java/org/apache/fop/pdf/PDFOutline.java
+++ b/src/java/org/apache/fop/pdf/PDFOutline.java
@@ -131,7 +131,6 @@
     protected byte[] toPDF() {
         ByteArrayOutputStream bout = new ByteArrayOutputStream(128);
         try {
-            bout.write(encode(getObjectID()));
             bout.write(encode("<<"));
             if (parent == null) {
                 // root Outlines object
@@ -164,7 +163,7 @@
                     bout.write(encode(" /A " + actionRef + "\n"));
                 }
             }
-            bout.write(encode(">> endobj\n"));
+            bout.write(encode(">>"));
         } catch (IOException ioe) {
             log.error("Ignored I/O exception", ioe);
         }
diff --git a/src/java/org/apache/fop/pdf/PDFOutputIntent.java b/src/java/org/apache/fop/pdf/PDFOutputIntent.java
index ea07382..1c03739 100644
--- a/src/java/org/apache/fop/pdf/PDFOutputIntent.java
+++ b/src/java/org/apache/fop/pdf/PDFOutputIntent.java
@@ -130,7 +130,6 @@
     public byte[] toPDF() {
         ByteArrayOutputStream bout = new ByteArrayOutputStream(128);
         try {
-            bout.write(encode(getObjectID()));
             bout.write(encode("<<\n"));
             bout.write(encode("/Type /OutputIntent\n"));
 
@@ -164,7 +163,7 @@
                 bout.write(encode("/DestOutputProfile " + destOutputProfile.referencePDF() + "\n"));
             }
 
-            bout.write(encode(">>\nendobj\n"));
+            bout.write(encode(">>"));
         } catch (IOException ioe) {
             log.error("Ignored I/O exception", ioe);
         }
diff --git a/src/java/org/apache/fop/pdf/PDFPages.java b/src/java/org/apache/fop/pdf/PDFPages.java
index 61d860e..98c293a 100644
--- a/src/java/org/apache/fop/pdf/PDFPages.java
+++ b/src/java/org/apache/fop/pdf/PDFPages.java
@@ -109,10 +109,9 @@
      */
     public String toPDFString() {
         StringBuffer sb = new StringBuffer(64);
-        sb.append(getObjectID())
-            .append("<< /Type /Pages\n/Count ")
-            .append(this.getCount())
-            .append("\n/Kids [");
+        sb.append("<< /Type /Pages\n/Count ")
+                .append(this.getCount())
+                .append("\n/Kids [");
         for (int i = 0; i < kids.size(); i++) {
             Object kid = kids.get(i);
             if (kid == null) {
@@ -120,7 +119,7 @@
             }
             sb.append(kid).append(" ");
         }
-        sb.append("] >>\nendobj\n");
+        sb.append("] >>");
         return sb.toString();
     }
 
diff --git a/src/java/org/apache/fop/pdf/PDFPattern.java b/src/java/org/apache/fop/pdf/PDFPattern.java
index 378b1cf..88a2ff4 100644
--- a/src/java/org/apache/fop/pdf/PDFPattern.java
+++ b/src/java/org/apache/fop/pdf/PDFPattern.java
@@ -210,13 +210,12 @@
      * @throws IOException if there is an error writing to the stream
      * @return the PDF string.
      */
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
 
         int vectorSize = 0;
         int tempInt = 0;
         byte[] buffer;
         StringBuffer p = new StringBuffer(64);
-        p.append(getObjectID());
         p.append("<< \n/Type /Pattern \n");
 
         if (this.resources != null) {
@@ -323,10 +322,6 @@
             length += pdfStream.outputStreamData(encodedStream, stream);
         }
 
-        buffer = encode("\nendobj\n");
-        stream.write(buffer);
-        length += buffer.length;
-
         return length;
     }
 
diff --git a/src/java/org/apache/fop/pdf/PDFResources.java b/src/java/org/apache/fop/pdf/PDFResources.java
index 70f9af5..df8647b 100644
--- a/src/java/org/apache/fop/pdf/PDFResources.java
+++ b/src/java/org/apache/fop/pdf/PDFResources.java
@@ -192,8 +192,8 @@
         return cs;
     }
 
-    /** {@inheritDoc} */
-    protected int output(OutputStream stream) throws IOException {
+    @Override
+    public int output(OutputStream stream) throws IOException {
         populateDictionary();
         return super.output(stream);
     }
diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java
index 81b93b1..e74c774 100644
--- a/src/java/org/apache/fop/pdf/PDFRoot.java
+++ b/src/java/org/apache/fop/pdf/PDFRoot.java
@@ -76,7 +76,7 @@
     }
 
     /** {@inheritDoc} */
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         getDocument().getProfile().verifyTaggedPDF();
         return super.output(stream);
     }
diff --git a/src/java/org/apache/fop/pdf/PDFShading.java b/src/java/org/apache/fop/pdf/PDFShading.java
index 2f955b8..9095396 100644
--- a/src/java/org/apache/fop/pdf/PDFShading.java
+++ b/src/java/org/apache/fop/pdf/PDFShading.java
@@ -342,8 +342,7 @@
         int vectorSize;
         int tempInt;
         StringBuffer p = new StringBuffer(128);
-        p.append(getObjectID()
-            + "<< \n/ShadingType " + this.shadingType + " \n");
+        p.append("<<\n/ShadingType " + this.shadingType + " \n");
         if (this.colorSpace != null) {
             p.append("/ColorSpace /"
                      + this.colorSpace.getName() + " \n");
@@ -528,7 +527,7 @@
 
         }
 
-        p.append(">> \nendobj\n");
+        p.append(">>");
 
         return (p.toString());
     }
diff --git a/src/java/org/apache/fop/pdf/PDFStream.java b/src/java/org/apache/fop/pdf/PDFStream.java
index 5f74a26..a0b990e 100644
--- a/src/java/org/apache/fop/pdf/PDFStream.java
+++ b/src/java/org/apache/fop/pdf/PDFStream.java
@@ -21,6 +21,7 @@
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.io.OutputStreamWriter;
 import java.io.Writer;
 
 /**
@@ -44,16 +45,33 @@
      * Create an empty stream object
      */
     public PDFStream() {
-        super();
+        setUp();
+    }
+
+    public PDFStream(PDFDictionary dictionary) {
+        super(dictionary);
+        setUp();
+    }
+
+    public PDFStream(PDFDictionary dictionary, boolean encodeOnTheFly) {
+        super(dictionary, encodeOnTheFly);
+        setUp();
+    }
+
+    public PDFStream(boolean encodeOnTheFly) {
+        super(encodeOnTheFly);
+        setUp();
+    }
+
+    private void setUp() {
         try {
             data = StreamCacheFactory.getInstance().createStreamCache();
-            this.streamWriter = new java.io.OutputStreamWriter(
+            this.streamWriter = new OutputStreamWriter(
                     getBufferOutputStream(), PDFDocument.ENCODING);
             //Buffer to minimize calls to the converter
             this.streamWriter = new java.io.BufferedWriter(this.streamWriter);
-        } catch (IOException ex) {
-            //TODO throw the exception and catch it elsewhere
-            ex.printStackTrace();
+        } catch (IOException e) {
+            throw new RuntimeException(e);
         }
     }
 
@@ -136,7 +154,7 @@
     /**
      * {@inheritDoc}
      */
-    protected int output(OutputStream stream) throws IOException {
+    public int output(OutputStream stream) throws IOException {
         final int len = super.output(stream);
 
         //Now that the data has been written, it can be discarded.
diff --git a/src/java/org/apache/fop/pdf/PDFStructElem.java b/src/java/org/apache/fop/pdf/PDFStructElem.java
index e35a860..90a41fb 100644
--- a/src/java/org/apache/fop/pdf/PDFStructElem.java
+++ b/src/java/org/apache/fop/pdf/PDFStructElem.java
@@ -31,7 +31,7 @@
 /**
  * Class representing a PDF Structure Element.
  */
-public class PDFStructElem extends PDFDictionary implements StructureTreeElement {
+public class PDFStructElem extends PDFDictionary implements StructureTreeElement, CompressedObject {
 
     private PDFStructElem parentElement;
 
diff --git a/src/java/org/apache/fop/pdf/PDFT1Stream.java b/src/java/org/apache/fop/pdf/PDFT1Stream.java
index d723625..2bae5dc 100644
--- a/src/java/org/apache/fop/pdf/PDFT1Stream.java
+++ b/src/java/org/apache/fop/pdf/PDFT1Stream.java
@@ -46,7 +46,7 @@
      * byte arrays around so much
      * {@inheritDoc}
      */
-    protected int output(java.io.OutputStream stream)
+    public int output(java.io.OutputStream stream)
             throws java.io.IOException {
         if (pfb == null) {
             throw new IllegalStateException("pfb must not be null at this point");
diff --git a/src/java/org/apache/fop/pdf/PDFTTFStream.java b/src/java/org/apache/fop/pdf/PDFTTFStream.java
index 643ddb1..998e14f 100644
--- a/src/java/org/apache/fop/pdf/PDFTTFStream.java
+++ b/src/java/org/apache/fop/pdf/PDFTTFStream.java
@@ -53,7 +53,7 @@
      * byte arrays around so much
      * {@inheritDoc}
      */
-    protected int output(java.io.OutputStream stream)
+    public int output(java.io.OutputStream stream)
             throws java.io.IOException {
         if (log.isDebugEnabled()) {
             log.debug("Writing " + origLength + " bytes of TTF font data");
diff --git a/src/java/org/apache/fop/pdf/PDFText.java b/src/java/org/apache/fop/pdf/PDFText.java
index 9566f60..3cddfe4 100644
--- a/src/java/org/apache/fop/pdf/PDFText.java
+++ b/src/java/org/apache/fop/pdf/PDFText.java
@@ -60,11 +60,9 @@
                 "The text of this PDFText must not be empty");
         }
         StringBuffer sb = new StringBuffer(64);
-        sb.append(getObjectID());
         sb.append("(");
         sb.append(escapeText(getText()));
         sb.append(")");
-        sb.append("\nendobj\n");
         return sb.toString();
     }
 
diff --git a/src/java/org/apache/fop/pdf/PDFUri.java b/src/java/org/apache/fop/pdf/PDFUri.java
index a6124ec..b3d377f 100644
--- a/src/java/org/apache/fop/pdf/PDFUri.java
+++ b/src/java/org/apache/fop/pdf/PDFUri.java
@@ -55,8 +55,7 @@
     /** {@inheritDoc} */
     public String toPDFString() {
         //TODO Convert this class into a dictionary
-        return getObjectID() + getDictString() + "\nendobj\n";
-        //throw new UnsupportedOperationException("This method should not be called");
+        return getDictString();
     }
 
 }
diff --git a/src/java/org/apache/fop/pdf/PDFXObject.java b/src/java/org/apache/fop/pdf/PDFXObject.java
index d1ce6d7..c2b7026 100644
--- a/src/java/org/apache/fop/pdf/PDFXObject.java
+++ b/src/java/org/apache/fop/pdf/PDFXObject.java
@@ -41,6 +41,10 @@
         super();
     }
 
+    protected PDFXObject(PDFDictionary dictionary) {
+        super(dictionary);
+    }
+
     /**
      * Returns the XObject's name.
      * @return the name of the XObject
diff --git a/src/java/org/apache/fop/pdf/Version.java b/src/java/org/apache/fop/pdf/Version.java
index 0df63d3..4bdc7a1 100644
--- a/src/java/org/apache/fop/pdf/Version.java
+++ b/src/java/org/apache/fop/pdf/Version.java
@@ -48,12 +48,13 @@
     }
 
     /**
-     * Given the PDF version as a String, returns the corresponding enumerated type. The String
-     * should be in the format "1.x" for PDF v1.x.
+     * Given the PDF version as a String, returns the corresponding enumerated type. The
+     * String should be in the format "1.x" for PDF v1.x.
      *
      * @param version a version number
      * @return the corresponding Version instance
-     * @throws IllegalArgumentException if the argument does not correspond to any existing PDF version
+     * @throws IllegalArgumentException if the argument does not correspond to any
+     * existing PDF version
      */
     public static Version getValueOf(String version) {
         for (Version pdfVersion : Version.values()) {
diff --git a/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java b/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java
new file mode 100644
index 0000000..eb619fc
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/xref/CompressedObjectReference.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * A reference to an indirect object stored in an object stream. Contains the relevant
+ * information to add to a cross-reference stream.
+ */
+public class CompressedObjectReference implements ObjectReference {
+
+    private final int objectNumber;
+
+    private final int objectStreamNumber;
+
+    private final int index;
+
+    /**
+     * Creates a new reference.
+     *
+     * @param objectNumber the number of the compressed object being referenced
+     * @param objectStreamNumber the number of the object stream in which the compressed
+     * object is to be found
+     * @param index the index of the compressed object in the object stream
+     */
+    public CompressedObjectReference(int objectNumber, int objectStreamNumber, int index) {
+        this.objectNumber = objectNumber;
+        this.objectStreamNumber = objectStreamNumber;
+        this.index = index;
+    }
+
+    public void output(DataOutputStream out) throws IOException {
+        out.write(2);
+        out.writeLong(objectStreamNumber);
+        out.write(0);
+        out.write(index);
+    }
+
+    public int getObjectNumber() {
+        return objectNumber;
+    }
+
+    public int getObjectStreamNumber() {
+        return objectStreamNumber;
+    }
+
+}
diff --git a/src/java/org/apache/fop/pdf/xref/CrossReferenceObject.java b/src/java/org/apache/fop/pdf/xref/CrossReferenceObject.java
new file mode 100644
index 0000000..3db50ec
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/xref/CrossReferenceObject.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A representation of the cross-reference data to be output at the end of a PDF file.
+ */
+public abstract class CrossReferenceObject {
+
+    protected final TrailerDictionary trailerDictionary;
+
+    protected final long startxref;
+
+    CrossReferenceObject(TrailerDictionary trailerDictionary, long startxref) {
+        this.trailerDictionary = trailerDictionary;
+        this.startxref = startxref;
+    }
+
+    /**
+     * Writes the cross reference data to a PDF stream
+     *
+     * @param stream the stream to write the cross reference to
+     * @throws IOException if an I/O exception occurs while writing the data
+     */
+    public abstract void output(OutputStream stream) throws IOException;
+}
diff --git a/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java b/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java
new file mode 100644
index 0000000..32c3157
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/xref/CrossReferenceStream.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.fop.pdf.PDFArray;
+import org.apache.fop.pdf.PDFDictionary;
+import org.apache.fop.pdf.PDFDocument;
+import org.apache.fop.pdf.PDFFilterList;
+import org.apache.fop.pdf.PDFName;
+import org.apache.fop.pdf.PDFStream;
+
+/**
+ * A cross-reference stream, as described in Section 3.4.7 of the PDF 1.5 Reference.
+ */
+public class CrossReferenceStream extends CrossReferenceObject {
+
+    private static final PDFName XREF = new PDFName("XRef");
+
+    private final PDFDocument document;
+
+    private final int objectNumber;
+
+    private final List<ObjectReference> objectReferences;
+
+    public CrossReferenceStream(PDFDocument document,
+            int objectNumber,
+            TrailerDictionary trailerDictionary,
+            long startxref,
+            List<Long> uncompressedObjectReferences,
+            List<CompressedObjectReference> compressedObjectReferences) {
+        super(trailerDictionary, startxref);
+        this.document = document;
+        this.objectNumber = objectNumber;
+        this.objectReferences = new ArrayList<ObjectReference>(uncompressedObjectReferences.size());
+        for (Long offset : uncompressedObjectReferences) {
+            objectReferences.add(offset == null ? null : new UncompressedObjectReference(offset));
+        }
+        for (CompressedObjectReference ref : compressedObjectReferences) {
+            this.objectReferences.set(ref.getObjectNumber() - 1, ref);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void output(OutputStream stream) throws IOException {
+        populateDictionary();
+        PDFStream helperStream = new PDFStream(trailerDictionary.getDictionary(), false) {
+
+            @Override
+            protected void setupFilterList() {
+                PDFFilterList filterList = getFilterList();
+                assert !filterList.isInitialized();
+                filterList.addDefaultFilters(document.getFilterMap(), getDefaultFilterName());
+            }
+
+        };
+        helperStream.setObjectNumber(objectNumber);
+        helperStream.setDocument(document);
+        ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
+        DataOutputStream data = new DataOutputStream(byteArray);
+        addFreeEntryForObject0(data);
+        for (ObjectReference objectReference : objectReferences) {
+            assert objectReference != null;
+            objectReference.output(data);
+        }
+        new UncompressedObjectReference(startxref).output(data);
+        data.close();
+        helperStream.setData(byteArray.toByteArray());
+        PDFDocument.outputIndirectObject(helperStream, stream);
+    }
+
+    private void populateDictionary() throws IOException {
+        int objectCount = objectReferences.size() + 1;
+        PDFDictionary dictionary = trailerDictionary.getDictionary();
+        dictionary.put("/Type", XREF);
+        dictionary.put("/Size", objectCount + 1);
+        dictionary.put("/W", new PDFArray(1, 8, 2));
+    }
+
+    private void addFreeEntryForObject0(DataOutputStream data) throws IOException {
+        data.write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xff, (byte) 0xff});
+    }
+
+}
diff --git a/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java b/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java
new file mode 100644
index 0000000..41a1146
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/xref/CrossReferenceTable.java
@@ -0,0 +1,73 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.apache.fop.pdf.PDFDictionary;
+import org.apache.fop.pdf.PDFDocument;
+
+/**
+ * A cross-reference table, as described in Section 3.4.3 of the PDF 1.5 Reference.
+ */
+public class CrossReferenceTable extends CrossReferenceObject {
+
+    private final List<Long> objectReferences;
+
+    private final StringBuilder pdf = new StringBuilder(256);
+
+    public CrossReferenceTable(TrailerDictionary trailerDictionary, long startxref,
+            List<Long> location) {
+        super(trailerDictionary, startxref);
+        this.objectReferences = location;
+    }
+
+    public void output(OutputStream stream) throws IOException {
+        outputXref();
+        writeTrailer(stream);
+    }
+
+    private void outputXref() throws IOException {
+        pdf.append("xref\n0 ");
+        pdf.append(objectReferences.size() + 1);
+        pdf.append("\n0000000000 65535 f \n");
+        for (Long objectReference : objectReferences) {
+            final String padding = "0000000000";
+            String s = String.valueOf(objectReference);
+            if (s.length() > 10) {
+                throw new IOException("PDF file too large."
+                        + " PDF 1.4 cannot grow beyond approx. 9.3GB.");
+            }
+            String loc = padding.substring(s.length()) + s;
+            pdf.append(loc).append(" 00000 n \n");
+        }
+    }
+
+    private void writeTrailer(OutputStream stream) throws IOException {
+        pdf.append("trailer\n");
+        stream.write(PDFDocument.encode(pdf.toString()));
+        PDFDictionary dictionary = trailerDictionary.getDictionary();
+        dictionary.put("/Size", objectReferences.size() + 1);
+        dictionary.output(stream);
+    }
+
+}
diff --git a/src/java/org/apache/fop/pdf/xref/ObjectReference.java b/src/java/org/apache/fop/pdf/xref/ObjectReference.java
new file mode 100644
index 0000000..894a66c
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/xref/ObjectReference.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * A reference to an indirect object.
+ */
+interface ObjectReference {
+
+    /**
+     * Outputs this reference to the given stream, in the cross-reference stream format.
+     * For example, a object may output the bytes 01 00 00 00 00 00 00 01 ff 00 to
+     * indicate a non-compressed object (01), at offset 511 from the beginning of the file
+     * (00 00 00 00 00 00 01 ff), of generation number 0 (00).
+     *
+     * @param out the stream to which to output the reference
+     */
+    void output(DataOutputStream out) throws IOException;
+}
diff --git a/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java b/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java
new file mode 100644
index 0000000..d5d6252
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/xref/TrailerDictionary.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+import org.apache.fop.pdf.PDFArray;
+import org.apache.fop.pdf.PDFDictionary;
+import org.apache.fop.pdf.PDFDocument;
+import org.apache.fop.pdf.PDFEncryption;
+import org.apache.fop.pdf.PDFInfo;
+import org.apache.fop.pdf.PDFRoot;
+import org.apache.fop.pdf.PDFText;
+import org.apache.fop.pdf.PDFWritable;
+
+/**
+ * A data class representing entries of the file trailer dictionary.
+ */
+public class TrailerDictionary {
+
+    private final PDFDictionary dictionary;
+
+    public TrailerDictionary(PDFDocument pdfDocument) {
+        this.dictionary = new PDFDictionary();
+        this.dictionary.setDocument(pdfDocument);
+    }
+
+    /** Sets the value of the Root entry. */
+    public TrailerDictionary setRoot(PDFRoot root) {
+        dictionary.put("/Root", root);
+        return this;
+    }
+
+    /** Sets the value of the Info entry. */
+    public TrailerDictionary setInfo(PDFInfo info) {
+        dictionary.put("/Info", info);
+        return this;
+    }
+
+    /** Sets the value of the Encrypt entry. */
+    public TrailerDictionary setEncryption(PDFEncryption encryption) {
+        dictionary.put("/Encrypt", encryption);
+        return this;
+    }
+
+    /** Sets the value of the ID entry. */
+    public TrailerDictionary setFileID(byte[] originalFileID, byte[] updatedFileID) {
+        // TODO this is ugly! Used to circumvent the fact that the file ID will be
+        // encrypted if directly stored as a byte array
+        class FileID implements PDFWritable {
+
+            private final byte[] fileID;
+
+            FileID(byte[] id) {
+                fileID = id;
+            }
+
+            public void outputInline(OutputStream out, StringBuilder textBuffer)
+                    throws IOException {
+                PDFDocument.flushTextBuffer(textBuffer, out);
+                String hex = PDFText.toHex(fileID, true);
+                byte[] encoded = hex.getBytes("US-ASCII");
+                out.write(encoded);
+            }
+
+        }
+        PDFArray fileID = new PDFArray(new FileID(originalFileID), new FileID(updatedFileID));
+        dictionary.put("/ID", fileID);
+        return this;
+    }
+
+    PDFDictionary getDictionary() {
+        return dictionary;
+    }
+
+}
diff --git a/src/java/org/apache/fop/pdf/xref/UncompressedObjectReference.java b/src/java/org/apache/fop/pdf/xref/UncompressedObjectReference.java
new file mode 100644
index 0000000..a54ec62
--- /dev/null
+++ b/src/java/org/apache/fop/pdf/xref/UncompressedObjectReference.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * A reference to an indirect object that is not stored in an object stream.
+ */
+class UncompressedObjectReference implements ObjectReference {
+
+    final long offset;
+
+    /**
+     * Creates a new reference.
+     *
+     * @param offset offset of the object from the beginning of the PDF file
+     */
+    UncompressedObjectReference(long offset) {
+        this.offset = offset;
+    }
+
+    public void output(DataOutputStream out) throws IOException {
+        out.write(1);
+        out.writeLong(offset);
+        out.write(0);
+        out.write(0);
+    }
+
+}
diff --git a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
index 88a6e9c..1c9f9b4 100644
--- a/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
+++ b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java
@@ -95,15 +95,15 @@
      */
     PDFLogicalStructureHandler(PDFDocument pdfDoc) {
         this.pdfDoc = pdfDoc;
-        PDFStructTreeRoot structTreeRoot = pdfDoc.getFactory().makeStructTreeRoot(parentTree);
-        rootStructureElement = pdfDoc.getFactory().makeStructureElement(
+        PDFStructTreeRoot structTreeRoot = pdfDoc.makeStructTreeRoot(parentTree);
+        rootStructureElement = pdfDoc.makeStructureElement(
                 FOToPDFRoleMap.mapFormattingObject("root", structTreeRoot), structTreeRoot);
         structTreeRoot.addKid(rootStructureElement);
     }
 
 
     PDFStructElem createPageSequence(Locale language) {
-        PDFStructElem structElemPart = pdfDoc.getFactory().makeStructureElement(
+        PDFStructElem structElemPart = pdfDoc.makeStructureElement(
                 FOToPDFRoleMap.mapFormattingObject("page-sequence", rootStructureElement),
                 rootStructureElement);
         rootStructureElement.addKid(structElemPart);
diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
index 83f6cca..53d2596 100644
--- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
+++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
@@ -385,8 +385,8 @@
         if (maxPDFVersion == null) {
             this.pdfDoc = new PDFDocument(producer);
         } else {
-            VersionController controller =
-                    VersionController.getFixedVersionController(maxPDFVersion);
+            VersionController controller
+                    = VersionController.getFixedVersionController(maxPDFVersion);
             this.pdfDoc = new PDFDocument(producer, controller);
         }
         updateInfo();
@@ -411,6 +411,9 @@
             log.debug("PDF/A is active. Conformance Level: " + pdfAMode);
             addPDFA1OutputIntent();
         }
+
+        this.pdfDoc.enableAccessibility(userAgent.isAccessibilityEnabled());
+
         return this.pdfDoc;
     }
 
diff --git a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
index 11eba4e..2a2a4a3 100644
--- a/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
+++ b/src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
@@ -65,7 +65,7 @@
         PDFStructElem parent = ancestors.getFirst();
         String role = attributes.getValue("role");
         PDFStructElem created;
-        created = pdfFactory.makeStructureElement(
+        created = pdfFactory.getDocument().makeStructureElement(
                 FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent);
         parent.addKid(created);
         ancestors.addFirst(created);
@@ -84,7 +84,7 @@
         PDFStructElem parent = ancestors.getFirst();
         String role = attributes.getValue("role");
         PDFStructElem created;
-        created = pdfFactory.makeStructureElement(
+        created = pdfFactory.getDocument().makeStructureElement(
                 FOToPDFRoleMap.mapFormattingObject(name, role, parent, eventBroadcaster), parent);
         parent.addKid(created);
         String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text");
@@ -104,7 +104,7 @@
         if ("#PCDATA".equals(name)) {
             created = new PDFStructElem.Placeholder(parent, name);
         } else {
-            created = pdfFactory.makeStructureElement(
+            created = pdfFactory.getDocument().makeStructureElement(
                     FOToPDFRoleMap.mapFormattingObject(name, role, parent,
                             eventBroadcaster), parent);
         }
diff --git a/test/accessibility/background-image_png_repeat.fo b/test/accessibility/background-image_png_repeat.fo
deleted file mode 100644
index 5e4a8ba..0000000
--- a/test/accessibility/background-image_png_repeat.fo
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0"?>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-<!-- $Id$ -->
-<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" language="en" country="GB">
-  <fo:layout-master-set>
-    <fo:simple-page-master master-name="page"
-      page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body background-image="../resources/images/bgimg72dpi.png"/>
-    </fo:simple-page-master>
-  </fo:layout-master-set>
-  <fo:page-sequence master-reference="page">
-    <fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify">
-      <fo:block>Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
-        formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
-        that reads a formatting object (FO) tree and renders the resulting pages to a specified 
-        output.</fo:block>
-    </fo:flow>
-  </fo:page-sequence>
-</fo:root>
diff --git a/test/accessibility/background-image_svg_repeat.fo b/test/accessibility/background-image_svg_repeat.fo
deleted file mode 100644
index 02520b6..0000000
--- a/test/accessibility/background-image_svg_repeat.fo
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0"?>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You under the Apache License, Version 2.0
-  (the "License"); you may not use this file except in compliance with
-  the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-  Unless required by applicable law or agreed to in writing, software
-  distributed under the License is distributed on an "AS IS" BASIS,
-  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  See the License for the specific language governing permissions and
-  limitations under the License.
--->
-<!-- $Id$ -->
-<fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" language="en" country="GB">
-  <fo:layout-master-set>
-    <fo:simple-page-master master-name="page"
-      page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body background-image="../resources/images/rgb-circles.svg"/>
-    </fo:simple-page-master>
-  </fo:layout-master-set>
-  <fo:page-sequence master-reference="page">
-    <fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify">
-      <fo:block>Apache FOP (Formatting Objects Processor) is a print formatter driven by XSL 
-        formatting objects (XSL-FO) and an output independent formatter. It is a Java application 
-        that reads a formatting object (FO) tree and renders the resulting pages to a specified 
-        output.</fo:block>
-    </fo:flow>
-  </fo:page-sequence>
-</fo:root>
diff --git a/test/accessibility/pdf/background-image_png_single.pdf b/test/accessibility/pdf/background-image_png_single.pdf
deleted file mode 100644
index 356e488..0000000
--- a/test/accessibility/pdf/background-image_png_single.pdf
+++ /dev/null
Binary files differ
diff --git a/test/accessibility/pdf/complete.pdf b/test/accessibility/pdf/complete.pdf
deleted file mode 100644
index cffb9e2..0000000
--- a/test/accessibility/pdf/complete.pdf
+++ /dev/null
Binary files differ
diff --git a/test/accessibility/pdf/image_png.pdf b/test/accessibility/pdf/image_png.pdf
deleted file mode 100644
index c328937..0000000
--- a/test/accessibility/pdf/image_png.pdf
+++ /dev/null
Binary files differ
diff --git a/test/accessibility/pdf/image_wmf.pdf b/test/accessibility/pdf/image_wmf.pdf
deleted file mode 100644
index b9ec8c5..0000000
--- a/test/accessibility/pdf/image_wmf.pdf
+++ /dev/null
Binary files differ
diff --git a/test/java/org/apache/fop/StandardTestSuite.java b/test/java/org/apache/fop/StandardTestSuite.java
index eecdeb6..8649fdf 100644
--- a/test/java/org/apache/fop/StandardTestSuite.java
+++ b/test/java/org/apache/fop/StandardTestSuite.java
@@ -34,9 +34,10 @@
 import org.apache.fop.image.loader.batik.ImageLoaderTestCase;
 import org.apache.fop.image.loader.batik.ImagePreloaderTestCase;
 import org.apache.fop.intermediate.IFMimickingTestCase;
+import org.apache.fop.layoutmgr.PageSequenceLayoutManagerTestCase;
+import org.apache.fop.pdf.PDFLibraryTestSuite;
 import org.apache.fop.render.extensions.prepress.PageBoundariesTestCase;
 import org.apache.fop.render.extensions.prepress.PageScaleTestCase;
-import org.apache.fop.layoutmgr.PageSequenceLayoutManagerTestCase;
 import org.apache.fop.render.pdf.PDFAConformanceTestCase;
 import org.apache.fop.render.pdf.PDFCMapTestCase;
 import org.apache.fop.render.pdf.PDFEncodingTestCase;
@@ -45,7 +46,6 @@
 import org.apache.fop.render.ps.PSTestSuite;
 import org.apache.fop.render.rtf.RichTextFormatTestSuite;
 import org.apache.fop.traits.MinOptMaxTestCase;
-import org.apache.fop.pdf.PDFLibraryTestSuite;
 
 /**
  * Test suite for basic functionality of FOP.
diff --git a/test/java/org/apache/fop/pdf/AbstractPDFStreamTestCase.java b/test/java/org/apache/fop/pdf/AbstractPDFStreamTestCase.java
index b930a8b..95d5c0a 100644
--- a/test/java/org/apache/fop/pdf/AbstractPDFStreamTestCase.java
+++ b/test/java/org/apache/fop/pdf/AbstractPDFStreamTestCase.java
@@ -48,10 +48,10 @@
             encodedBytes[i++] = (byte) (in & 0xff);
         }
     }
-    private String startStream = "1 0 obj\n" +
-                                "<< /Length 5 0 R /Filter /FlateDecode >>\n" +
-                                "stream\n";
-    private String endStream = "endstream\nendobj\n";
+    private String startStream = "<< /Length 5 0 R /Filter /FlateDecode >>\n"
+                + "stream\n";
+
+    private String endStream = "endstream";
 
     @Before
     public void setUp() {
diff --git a/test/java/org/apache/fop/pdf/ObjectStreamManagerTestCase.java b/test/java/org/apache/fop/pdf/ObjectStreamManagerTestCase.java
new file mode 100644
index 0000000..89d9800
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/ObjectStreamManagerTestCase.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import org.junit.Test;
+
+import org.apache.fop.pdf.xref.CompressedObjectReference;
+
+public class ObjectStreamManagerTestCase {
+
+    private List<CompressedObjectReference> compressedObjectReferences;
+
+    private MockPdfDocument pdfDocument;
+
+    @Test
+    public void add() {
+        final int expectedCapacity = 100;
+        final int numCompressedObjects = expectedCapacity * 2 + 1;
+        createCompressObjectReferences(numCompressedObjects);
+        assertEquals(numCompressedObjects, compressedObjectReferences.size());
+        int objectStreamNumber1 = assertSameObjectStream(0, expectedCapacity);
+        int objectStreamNumber2 = assertSameObjectStream(expectedCapacity, expectedCapacity * 2);
+        int objectStreamNumber3 = assertSameObjectStream(expectedCapacity * 2, numCompressedObjects);
+        assertDifferent(objectStreamNumber1, objectStreamNumber2, objectStreamNumber3);
+        assertEquals(objectStreamNumber3, pdfDocument.previous.getObjectNumber());
+    }
+
+    private void createCompressObjectReferences(int numObjects) {
+        pdfDocument = new MockPdfDocument();
+        ObjectStreamManager sut = new ObjectStreamManager(pdfDocument);
+        for (int obNum = 1; obNum <= numObjects; obNum++) {
+            sut.add(createCompressedObject(obNum));
+        }
+        compressedObjectReferences = sut.getCompressedObjectReferences();
+    }
+
+    private static class MockPdfDocument extends PDFDocument {
+
+        private ObjectStream previous;
+
+        public MockPdfDocument() {
+            super("");
+        }
+
+        public void assignObjectNumber(PDFObject obj) {
+            super.assignObjectNumber(obj);
+            if (obj instanceof ObjectStream) {
+                ObjectStream  objStream = (ObjectStream) obj;
+                ObjectStream previous = (ObjectStream) objStream.get("Extends");
+                if (previous == null) {
+                    assertEquals(this.previous, previous);
+                }
+                this.previous = objStream;
+            }
+        }
+    }
+
+    private CompressedObject createCompressedObject(final int objectNumber) {
+        return new CompressedObject() {
+
+            public int getObjectNumber() {
+                return objectNumber;
+            }
+
+            public int output(OutputStream outputStream) throws IOException {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    private int assertSameObjectStream(int from, int to) {
+        int objectStreamNumber = getObjectStreamNumber(from);
+        for (int i = from + 1; i < to; i++) {
+            assertEquals(objectStreamNumber, getObjectStreamNumber(i));
+        }
+        return objectStreamNumber;
+    }
+
+    private int getObjectStreamNumber(int index) {
+        return compressedObjectReferences.get(index).getObjectStreamNumber();
+    }
+
+    private void assertDifferent(int objectStreamNumber1, int objectStreamNumber2,
+            int objectStreamNumber3) {
+        assertTrue(objectStreamNumber1 != objectStreamNumber2);
+        assertTrue(objectStreamNumber1 != objectStreamNumber3);
+        assertTrue(objectStreamNumber2 != objectStreamNumber3);
+    }
+}
diff --git a/test/java/org/apache/fop/pdf/ObjectStreamTestCase.java b/test/java/org/apache/fop/pdf/ObjectStreamTestCase.java
new file mode 100644
index 0000000..317828e
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/ObjectStreamTestCase.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class ObjectStreamTestCase {
+
+    private static final String OBJECT_CONTENT = "<<\n  /Foo True\n  /Bar False\n>>\n";
+
+    private PDFDocument pdfDocument;
+
+    private ObjectStream objectStream;
+
+    private List<MockCompressedObject> compressedObjects;
+
+    @Before
+    public void setUp() throws Exception {
+        pdfDocument = new PDFDocument("PDFObjectStreamTestCase");
+        objectStream = new ObjectStream();
+        pdfDocument.assignObjectNumber(objectStream);
+        compressedObjects = Arrays.asList(new MockCompressedObject(), new MockCompressedObject());
+    }
+
+    @Test
+    public void testSingleObjectStream() throws IOException {
+        populateObjectStream();
+        testOutput();
+    }
+
+    @Test
+    public void testObjectStreamCollection() throws IOException {
+        objectStream = new ObjectStream(objectStream);
+        pdfDocument.assignObjectNumber(objectStream);
+        populateObjectStream();
+        testOutput();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void directObjectsAreNotAllowed() throws Exception {
+        objectStream.addObject(new MockCompressedObject());
+    }
+
+    @Test(expected = NullPointerException.class)
+    public void nullObjectsAreNotAllowed() throws Exception {
+        objectStream.addObject(null);
+    }
+
+    private void testOutput() throws IOException {
+        String expected = getExpectedOutput();
+        String actual = getActualOutput();
+        assertEquals(expected, actual);
+    }
+
+    private void populateObjectStream() {
+        for (MockCompressedObject obj : compressedObjects) {
+            pdfDocument.assignObjectNumber(obj);
+            objectStream.addObject(obj);
+        }
+    }
+
+    private String getExpectedOutput() {
+        int numObs = compressedObjects.size();
+        int objectStreamNumber = objectStream.getObjectNumber();
+        int offsetsLength = 9;
+        StringBuilder expected = new StringBuilder();
+        expected.append("<<\n");
+        ObjectStream previous = (ObjectStream) objectStream.get("Extends");
+        if (previous != null) {
+            expected.append("  /Extends ").append(previous.getObjectNumber()).append(" 0 R\n");
+        }
+        expected.append("  /Type /ObjStm\n")
+                .append("  /N ").append(numObs).append("\n")
+                .append("  /First ").append(offsetsLength).append('\n')
+                .append("  /Length ").append(OBJECT_CONTENT.length() * 2 + offsetsLength + 1).append('\n')
+                .append(">>\n")
+                .append("stream\n");
+        int offset = 0;
+        int num = 1;
+        for (PDFObject ob : compressedObjects) {
+            expected.append(objectStreamNumber + num++).append(' ').append(offset).append('\n');
+            offset += ob.toPDFString().length();
+        }
+        for (PDFObject ob : compressedObjects) {
+            expected.append(ob.toPDFString());
+        }
+        expected.append("\nendstream");
+        return expected.toString();
+    }
+
+    private String getActualOutput() throws IOException {
+        ByteArrayOutputStream actual = new ByteArrayOutputStream();
+        objectStream.getFilterList().setDisableAllFilters(true);
+        objectStream.output(actual);
+        return actual.toString("US-ASCII");
+    }
+
+    private static class MockCompressedObject extends PDFObject implements CompressedObject {
+
+        @Override
+        protected String toPDFString() {
+            return OBJECT_CONTENT;
+        }
+    }
+
+}
diff --git a/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java
index 235db70..db10e65 100644
--- a/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java
+++ b/test/java/org/apache/fop/pdf/PDFEncryptionJCETestCase.java
@@ -223,8 +223,6 @@
             final String digits = "\\d+";
             final String hexDigits = "\\p{XDigit}+";
 
-            dictionary.mustContain("1" + whitespace + "0" + whitespace + "obj");
-
             dictionary.mustContain("/Filter" + whitespace + "/Standard\\b");
 
             dictionary.mustContain("/V" + whitespace + "(" + digits + ")")
diff --git a/test/java/org/apache/fop/pdf/PDFFilterListTestCase.java b/test/java/org/apache/fop/pdf/PDFFilterListTestCase.java
new file mode 100644
index 0000000..2504d87
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/PDFFilterListTestCase.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import static org.junit.Assert.assertFalse;
+
+import org.junit.Test;
+
+public class PDFFilterListTestCase {
+
+    @Test
+    public void testFilterList() {
+        PDFFilterList filterList = new PDFFilterList();
+        assertFalse(filterList.isInitialized());
+    }
+}
diff --git a/test/java/org/apache/fop/pdf/PDFLibraryTestSuite.java b/test/java/org/apache/fop/pdf/PDFLibraryTestSuite.java
index a0c4ea1..c7a9dff 100644
--- a/test/java/org/apache/fop/pdf/PDFLibraryTestSuite.java
+++ b/test/java/org/apache/fop/pdf/PDFLibraryTestSuite.java
@@ -40,7 +40,9 @@
         PDFNullTestCase.class,
         PDFNumsArrayTestCase.class,
         PDFRectangleTestCase.class,
-        PDFReferenceTestCase.class
+        PDFReferenceTestCase.class,
+        VersionTestCase.class,
+        VersionControllerTestCase.class
 })
 public class PDFLibraryTestSuite {
 }
diff --git a/test/java/org/apache/fop/pdf/PDFObjectTestCase.java b/test/java/org/apache/fop/pdf/PDFObjectTestCase.java
index ee9512d..10ffa3b 100644
--- a/test/java/org/apache/fop/pdf/PDFObjectTestCase.java
+++ b/test/java/org/apache/fop/pdf/PDFObjectTestCase.java
@@ -40,10 +40,6 @@
     protected final PDFObject parent = new DummyPDFObject();
     /** The test subject */
     protected PDFObject pdfObjectUnderTest;
-    /** The string to begin describing the object <code>"1 0 obj\n"</code> */
-    protected final String beginObj = "1 0 obj\n";
-    /** The string to end describing the object <code>"\nendobj\n"</code> */
-    protected final String endObj = "\nendobj\n";
 
     private static class DummyPDFObject extends PDFObject {
 
@@ -195,8 +191,7 @@
         outStream.reset();
         object.setObjectNumber(1);
         // Test the length of the output string is returned correctly.
-        String string = beginObj + expectedString + endObj;
-        assertEquals(string.length(), object.output(outStream));
-        assertEquals(string, outStream.toString());
+        assertEquals(expectedString.length(), object.output(outStream));
+        assertEquals(expectedString, outStream.toString());
     }
 }
diff --git a/test/java/org/apache/fop/pdf/PDFStreamTestCase.java b/test/java/org/apache/fop/pdf/PDFStreamTestCase.java
new file mode 100644
index 0000000..93dcea5
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/PDFStreamTestCase.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class PDFStreamTestCase {
+
+    private PDFStream stream;
+
+    @Before
+    public void createStream() {
+        stream = new PDFStream();
+        stream.setObjectNumber(1);
+        PDFDocument pdfDocument = new PDFDocument("Apache FOP");
+        stream.setDocument(pdfDocument);
+    }
+
+    @Test
+    public void testFilterSetup() {
+        testGetFilterList();
+        testSetupFilterList();
+    }
+
+    private void testGetFilterList() {
+        PDFFilterList filterList = stream.getFilterList();
+        assertFalse(filterList.isInitialized());
+        assertEquals(0, filterList.getFilters().size());
+    }
+
+    private void testSetupFilterList() {
+        stream.setupFilterList();
+        PDFFilterList filterList = stream.getFilterList();
+        assertTrue(filterList.isInitialized());
+        assertEquals(1, filterList.getFilters().size());
+        PDFFilter filter = filterList.getFilters().get(0);
+        assertEquals("/FlateDecode", filter.getName());
+    }
+
+    @Test
+    public void customFilter() {
+        PDFFilterList filters = stream.getFilterList();
+        filters.addFilter("null");
+        assertTrue(filters.isInitialized());
+        assertEquals(1, filters.getFilters().size());
+        PDFFilter filter = filters.getFilters().get(0);
+        assertEquals("", filter.getName());
+    }
+
+    @Test
+    public void testStream() throws IOException {
+        PDFFilterList filters = stream.getFilterList();
+        filters.addFilter("null");
+        byte[] bytes = createSampleData();
+        stream.setData(bytes);
+        ByteArrayOutputStream actual = new ByteArrayOutputStream();
+        stream.outputRawStreamData(actual);
+        assertArrayEquals(bytes, actual.toByteArray());
+    }
+
+    @Test
+    public void testEncodeStream() throws IOException {
+        PDFFilterList filters = stream.getFilterList();
+        filters.addFilter("null");
+        byte[] bytes = createSampleData();
+        stream.setData(bytes);
+        ByteArrayOutputStream actual = new ByteArrayOutputStream();
+        StreamCache streamCache = stream.encodeStream();
+        streamCache.outputContents(actual);
+        assertArrayEquals(bytes, actual.toByteArray());
+    }
+
+    @Test
+    public void testEncodeAndWriteStream() throws IOException {
+        PDFFilterList filters = stream.getFilterList();
+        filters.addFilter("null");
+        byte[] bytes = createSampleData();
+        stream.setData(bytes);
+        ByteArrayOutputStream actual = new ByteArrayOutputStream();
+        PDFNumber number = new PDFNumber();
+        stream.encodeAndWriteStream(actual, number);
+        assertArrayEquals(createSampleStreamData(), actual.toByteArray());
+    }
+
+    private byte[] createSampleData() {
+        byte[] bytes = new byte[10];
+        for (int i = 0; i < 10; i++) {
+            bytes[i] = (byte) i;
+        }
+        return bytes;
+    }
+
+    private byte[] createSampleStreamData() throws IOException {
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        stream.write("stream\n".getBytes("US-ASCII"));
+        stream.write(createSampleData());
+        stream.write("\nendstream".getBytes("US-ASCII"));
+        return stream.toByteArray();
+    }
+}
diff --git a/test/java/org/apache/fop/pdf/xref/CompressedObjectReferenceTestCase.java b/test/java/org/apache/fop/pdf/xref/CompressedObjectReferenceTestCase.java
new file mode 100644
index 0000000..8b103d2
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/xref/CompressedObjectReferenceTestCase.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+
+public class CompressedObjectReferenceTestCase extends ObjectReferenceTest {
+
+    @Test
+    public void testOutput() throws IOException {
+        runTest(Arrays.asList(0, 0, 0, 0, 0, 0, 0, 0), 0);
+        runTest(Arrays.asList(0, 0, 0, 0, 0, 0, 0, 0x1), 4);
+        runTest(Arrays.asList(0, 0, 0, 0, 0, 0, 0, 0xf3), 16);
+        runTest(Arrays.asList(0, 0, 0, 0, 0, 0, 0x5, 0xf7), 128);
+        runTest(Arrays.asList(0, 0, 0, 0, 0, 0x9, 0xfb, 0xd), 0xae);
+        runTest(Arrays.asList(0, 0, 0, 0, 0x11, 0xff, 0x15, 0xe9), 0xff);
+    }
+
+    private void runTest(List<Integer> expectedObjectStreamBytes, int index) throws IOException {
+        int objectStreamNumber = (int) computeNumberFromBytes(expectedObjectStreamBytes);
+        sut = new CompressedObjectReference(0, objectStreamNumber, index);
+        byte[] expected = createExpectedOutput((byte) 2, expectedObjectStreamBytes, index);
+        byte[] actual = getActualOutput();
+        assertArrayEquals(expected, actual);
+    }
+
+}
diff --git a/test/java/org/apache/fop/pdf/xref/CrossReferenceObjectTest.java b/test/java/org/apache/fop/pdf/xref/CrossReferenceObjectTest.java
new file mode 100644
index 0000000..df1b86e
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/xref/CrossReferenceObjectTest.java
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.Before;
+
+import org.apache.fop.pdf.PDFDocument;
+import org.apache.fop.pdf.PDFInfo;
+import org.apache.fop.pdf.PDFPages;
+import org.apache.fop.pdf.PDFRoot;
+
+public abstract class CrossReferenceObjectTest {
+
+    protected static final int STARTXREF = 12345;
+
+    protected PDFDocument pdfDocument;
+
+    protected TrailerDictionary trailerDictionary;
+
+    private CrossReferenceObject crossReferenceObject;
+
+    @Before
+    public void setUp() throws UnsupportedEncodingException {
+        pdfDocument = new PDFDocument("Apache FOP");
+        Map<String, List<String>> filterMap = pdfDocument.getFilterMap();
+        filterMap.put("default", Arrays.asList("null"));
+        PDFRoot root = new PDFRoot(1, new PDFPages(10));
+        PDFInfo info = new PDFInfo();
+        info.setObjectNumber(2);
+        byte[] fileID =
+                new byte[] {0x01, 0x23, 0x45, 0x67, (byte) 0x89, (byte) 0xab, (byte) 0xcd, (byte) 0xef};
+        trailerDictionary = new TrailerDictionary(pdfDocument)
+                .setRoot(root)
+                .setInfo(info)
+                .setFileID(fileID, fileID);
+    }
+
+    protected void runTest() throws IOException {
+        crossReferenceObject = createCrossReferenceObject();
+        byte[] expected = createExpectedCrossReferenceData();
+        byte[] actual = createActualCrossReferenceData();
+        assertArrayEquals(expected, actual);
+    }
+
+    protected abstract CrossReferenceObject createCrossReferenceObject();
+
+    protected abstract byte[] createExpectedCrossReferenceData() throws IOException;
+
+    protected byte[] createActualCrossReferenceData() throws IOException {
+        ByteArrayOutputStream pdf = new ByteArrayOutputStream();
+        crossReferenceObject.output(pdf);
+        pdf.close();
+        return pdf.toByteArray();
+    }
+
+    protected byte[] getBytes(StringBuilder stringBuilder) {
+        return getBytes(stringBuilder.toString());
+    }
+
+    protected byte[] getBytes(String string) {
+        try {
+            return string.getBytes("US-ASCII");
+        } catch (UnsupportedEncodingException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Outputs the given byte array to a file with the given name. Use for debugging
+     * purpose.
+     */
+    protected void streamToFile(byte[] bytes, String filename) throws IOException {
+        OutputStream output = new FileOutputStream(filename);
+        output.write(bytes);
+        output.close();
+    }
+
+}
diff --git a/test/java/org/apache/fop/pdf/xref/CrossReferenceStreamTestCase.java b/test/java/org/apache/fop/pdf/xref/CrossReferenceStreamTestCase.java
new file mode 100644
index 0000000..3e60963
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/xref/CrossReferenceStreamTestCase.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+public class CrossReferenceStreamTestCase extends CrossReferenceObjectTest {
+
+    private List<Long> uncompressedObjectOffsets;
+
+    private List<CompressedObjectReference> compressedObjectReferences;
+
+    @Test
+    public void testWithNoOffset() throws IOException {
+        List<Long> emptyList = Collections.emptyList();
+        test(emptyList);
+    }
+
+    @Test
+    public void testWithOffsets() throws IOException {
+        test(new ArrayList<Long>(Arrays.asList(0L, 1L, 2L, 3L, 4L)));
+    }
+
+    @Test
+    public void testWithBigOffsets() throws IOException {
+        test(new ArrayList<Long>(Arrays.asList(0xffL, 0xffffL, 0xffffffffL, 0xffffffffffffffffL)));
+    }
+
+    @Test
+    public void testWithObjectStreams1() throws IOException {
+        List<CompressedObjectReference> compressedObjectReferences =
+                Arrays.asList(new CompressedObjectReference(2, 1, 0));
+        test(Arrays.asList(0L, null), compressedObjectReferences);
+    }
+
+    @Test
+    public void testWithObjectStreams2() throws IOException {
+        int numIndirectObjects = 2;
+        int numCompressedObjects = 1;
+        List<Long> indirectObjectOffsets
+                = new ArrayList<Long>(numIndirectObjects + numCompressedObjects);
+        for (long i = 0; i < numIndirectObjects; i++) {
+            indirectObjectOffsets.add(i);
+        }
+        List<CompressedObjectReference> compressedObjectReferences
+                = new ArrayList<CompressedObjectReference>();
+        for (int index = 0; index < numCompressedObjects; index++) {
+            indirectObjectOffsets.add(null);
+            int obNum = numIndirectObjects + index + 1;
+            compressedObjectReferences.add(new CompressedObjectReference(obNum,
+                    numIndirectObjects, index));
+        }
+        test(indirectObjectOffsets, compressedObjectReferences);
+    }
+
+    private void test(List<Long> indirectObjectOffsets) throws IOException {
+        List<CompressedObjectReference> compressedObjectReferences = Collections.emptyList();
+        test(indirectObjectOffsets, compressedObjectReferences);
+    }
+
+    private void test(List<Long> indirectObjectOffsets,
+            List<CompressedObjectReference> compressedObjectReferences) throws IOException {
+        this.uncompressedObjectOffsets = indirectObjectOffsets;
+        this.compressedObjectReferences = compressedObjectReferences;
+        runTest();
+    }
+
+    @Override
+    protected CrossReferenceObject createCrossReferenceObject() {
+        return new CrossReferenceStream(pdfDocument,
+                uncompressedObjectOffsets.size() + 1,
+                trailerDictionary,
+                STARTXREF,
+                uncompressedObjectOffsets,
+                compressedObjectReferences);
+    }
+
+    @Override
+    protected byte[] createExpectedCrossReferenceData() throws IOException {
+        List<ObjectReference> objectReferences
+                = new ArrayList<ObjectReference>(uncompressedObjectOffsets.size());
+        for (Long offset : uncompressedObjectOffsets) {
+            objectReferences.add(offset == null ? null : new UncompressedObjectReference(offset));
+        }
+        for (CompressedObjectReference ref : compressedObjectReferences) {
+            objectReferences.set(ref.getObjectNumber() - 1, ref);
+        }
+        int maxObjectNumber = objectReferences.size() + 1;
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        StringBuilder expected = new StringBuilder(256);
+        expected.append(maxObjectNumber + " 0 obj\n")
+                .append("<<\n")
+                .append("  /Root 1 0 R\n")
+                .append("  /Info 2 0 R\n")
+                .append("  /ID [<0123456789ABCDEF> <0123456789ABCDEF>]\n")
+                .append("  /Type /XRef\n")
+                .append("  /Size ").append(Integer.toString(maxObjectNumber + 1)).append('\n')
+                .append("  /W [1 8 2]\n")
+                .append("  /Length ").append(Integer.toString((maxObjectNumber + 1) * 11 + 1)).append('\n')
+                .append(">>\n")
+                .append("stream\n");
+        stream.write(getBytes(expected));
+        DataOutputStream data = new DataOutputStream(stream);
+        data.write(new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, (byte) 0xff, (byte) 0xff});
+        for (ObjectReference objectReference : objectReferences) {
+            objectReference.output(data);
+        }
+        data.write(1);
+        data.writeLong(STARTXREF);
+        data.write(0);
+        data.write(0);
+        data.close();
+        stream.write(getBytes("\nendstream\nendobj\n"));
+        return stream.toByteArray();
+    }
+
+}
diff --git a/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java b/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java
new file mode 100644
index 0000000..ceff96a
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/xref/CrossReferenceTableTestCase.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+public class CrossReferenceTableTestCase extends CrossReferenceObjectTest {
+
+    private List<Long> offsets;
+
+    @Test
+    public void testWithNoOffset() throws IOException {
+        List<Long> emptyList = Collections.emptyList();
+        runTest(emptyList);
+    }
+
+    @Test
+    public void testWithOffsets() throws IOException {
+        runTest(Arrays.asList(0L, 1L, 2L, 3L, 4L));
+    }
+
+    @Test
+    public void testWithBigOffsets() throws IOException {
+        runTest(Arrays.asList(0xffL, 0xffffL, 0x7fffffffL));
+    }
+
+    private void runTest(List<Long> offsets) throws IOException {
+        this.offsets = offsets;
+        runTest();
+    }
+
+    @Override
+    protected CrossReferenceObject createCrossReferenceObject() {
+        return new CrossReferenceTable(trailerDictionary, STARTXREF, offsets);
+    }
+
+    @Override
+    protected byte[] createExpectedCrossReferenceData() throws IOException {
+        StringBuilder expected = new StringBuilder(256);
+        expected.append("xref\n0 ")
+                .append(offsets.size() + 1)
+                .append("\n0000000000 65535 f \n");
+        for (Long objectReference : offsets) {
+            final String padding = "0000000000";
+            String s = String.valueOf(objectReference).toString();
+            String loc = padding.substring(s.length()) + s;
+            expected.append(loc).append(" 00000 n \n");
+        }
+        expected.append("trailer\n<<\n")
+                .append("  /Root 1 0 R\n")
+                .append("  /Info 2 0 R\n")
+                .append("  /ID [<0123456789ABCDEF> <0123456789ABCDEF>]\n")
+                .append("  /Size ").append(Integer.toString(offsets.size() + 1)).append('\n')
+                .append(">>\n");
+        return getBytes(expected);
+    }
+
+}
diff --git a/test/java/org/apache/fop/pdf/xref/ObjectReferenceTest.java b/test/java/org/apache/fop/pdf/xref/ObjectReferenceTest.java
new file mode 100644
index 0000000..fada279
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/xref/ObjectReferenceTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.util.List;
+
+abstract class ObjectReferenceTest {
+
+    protected ObjectReference sut;
+
+    protected long computeNumberFromBytes(List<Integer> expectedOffsetBytes) {
+        assert expectedOffsetBytes.size() <= 8;
+        long offset = 0;
+        for (int b : expectedOffsetBytes) {
+            offset = offset << 8 | b;
+        }
+        return offset;
+    }
+
+    protected byte[] createExpectedOutput(byte field1, List<Integer> field2, int field3) {
+        assert field2.size() == 8;
+        assert (field3 & 0xffff) == field3;
+        byte[] expected = new byte[11];
+        int index = 0;
+        expected[index++] = field1;
+        for (Integer b : field2) {
+            expected[index++] = b.byteValue();
+        }
+        expected[index++] = (byte) ((field3 & 0xff00) >> 8);
+        expected[index++] = (byte) (field3 & 0xff);
+        return expected;
+    }
+
+    protected byte[] getActualOutput() throws IOException {
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        DataOutputStream dataOutputStream = new DataOutputStream(out);
+        sut.output(dataOutputStream);
+        dataOutputStream.close();
+        return out.toByteArray();
+    }
+
+}
diff --git a/test/java/org/apache/fop/pdf/xref/UncompressedObjectReferenceTestCase.java b/test/java/org/apache/fop/pdf/xref/UncompressedObjectReferenceTestCase.java
new file mode 100644
index 0000000..b147084
--- /dev/null
+++ b/test/java/org/apache/fop/pdf/xref/UncompressedObjectReferenceTestCase.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf.xref;
+
+import static org.junit.Assert.assertArrayEquals;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+
+public class UncompressedObjectReferenceTestCase extends ObjectReferenceTest {
+
+    @Test
+    public void test1ByteOffsets() throws IOException {
+        run1ByteOffsetTest(0x0);
+        run1ByteOffsetTest(0xf);
+        run1ByteOffsetTest(0x10);
+        run1ByteOffsetTest(0xff);
+    }
+
+    private void run1ByteOffsetTest(int offset) throws IOException {
+        runIntegerOffsetTest(Arrays.asList(0, 0, 0, offset));
+    }
+
+    @Test
+    public void test2ByteOffsets() throws IOException {
+        runIntegerOffsetTest(Arrays.asList(0, 0, 1, 0xff));
+        runIntegerOffsetTest(Arrays.asList(0, 0, 0xa0, 0xff));
+    }
+
+    @Test
+    public void test3ByteOffsets() throws IOException {
+        runIntegerOffsetTest(Arrays.asList(0, 2, 0x12, 0x34));
+        runIntegerOffsetTest(Arrays.asList(0, 0xee, 0x56, 0x78));
+    }
+
+    @Test
+    public void test4ByteOffsets() throws IOException {
+        runIntegerOffsetTest(Arrays.asList(0x6, 0x12, 0x34, 0x56));
+        runIntegerOffsetTest(Arrays.asList(0xf1, 0x9a, 0xbc, 0xde));
+    }
+
+    @Test
+    public void test5ByteOffsets() throws IOException {
+        runTest(Arrays.asList(0, 0, 0, 0x7, 0x78, 0x9a, 0xbc, 0xde));
+        runTest(Arrays.asList(0, 0, 0, 0xbf, 0xf0, 0, 0x1, 0x2));
+    }
+
+    @Test
+    public void test8ByteOffsets() throws IOException {
+        runTest(Arrays.asList(0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8));
+        runTest(Arrays.asList(0xf9, 0xe8, 0xd7, 0xc6, 0xb5, 0xa4, 0x93, 0x82));
+    }
+
+    private void runIntegerOffsetTest(List<Integer> expectedOffsetBytes) throws IOException {
+        List<Integer> expectedLongOffset = new ArrayList<Integer>(8);
+        expectedLongOffset.addAll(Arrays.asList(0, 0, 0, 0));
+        expectedLongOffset.addAll(expectedOffsetBytes);
+        runTest(expectedLongOffset);
+    }
+
+    private void runTest(List<Integer> expectedOffsetBytes) throws IOException {
+        long offset = computeNumberFromBytes(expectedOffsetBytes);
+        sut = new UncompressedObjectReference(offset);
+        byte[] expected = createExpectedOutput((byte) 1, expectedOffsetBytes, (byte) 0);
+        byte[] actual = getActualOutput();
+        assertArrayEquals(expected, actual);
+    }
+
+}
diff --git a/test/accessibility/fop.xconf b/test/pdf/1.5/fop.xconf
similarity index 87%
copy from test/accessibility/fop.xconf
copy to test/pdf/1.5/fop.xconf
index 8c5dc2b..ab8bc7c 100644
--- a/test/accessibility/fop.xconf
+++ b/test/pdf/1.5/fop.xconf
@@ -3,9 +3,10 @@
   <accessibility>true</accessibility>
   <source-resolution>144</source-resolution>
   <use-cache>false</use-cache>
-  <font-base>../resources/fonts/</font-base>
+  <font-base>../../resources/fonts/ttf/</font-base>
   <renderers>
     <renderer mime="application/pdf">
+      <version>1.5</version>
       <filterList>
         <value>null</value>
       </filterList>
diff --git a/test/accessibility/complete.fo b/test/pdf/1.5/test.fo
similarity index 98%
rename from test/accessibility/complete.fo
rename to test/pdf/1.5/test.fo
index 7568475..23e8405 100644
--- a/test/accessibility/complete.fo
+++ b/test/pdf/1.5/test.fo
@@ -45,7 +45,7 @@
                   external-destination="http://xmlgraphics.apache.org/fop/">FOP 
                   website</fo:basic-link></fo:wrapper> for more 
               information</fo:block></fo:footnote-body></fo:footnote>. FOP has a nice logo: 
-        <fo:external-graphic src="../resources/images/fop-logo-color-24bit.png" 
+        <fo:external-graphic src="../../resources/images/fop-logo-color-24bit.png" 
           inline-progression-dimension.maximum="100%" content-width="scale-to-fit"
           fox:alt-text="FOP Logo"/></fo:block>
       <fo:table space-before="10pt" space-after="10pt" width="100%" table-layout="fixed">
diff --git a/test/pdf/1.5/test.pdf b/test/pdf/1.5/test.pdf
new file mode 100644
index 0000000..4c25c08
--- /dev/null
+++ b/test/pdf/1.5/test.pdf
Binary files differ
diff --git a/test/accessibility/README b/test/pdf/accessibility/README
similarity index 100%
rename from test/accessibility/README
rename to test/pdf/accessibility/README
diff --git a/test/accessibility/background-image_jpg_repeat.fo b/test/pdf/accessibility/background-image_jpg_repeat.fo
similarity index 94%
copy from test/accessibility/background-image_jpg_repeat.fo
copy to test/pdf/accessibility/background-image_jpg_repeat.fo
index 727162e..12af7d7 100644
--- a/test/accessibility/background-image_jpg_repeat.fo
+++ b/test/pdf/accessibility/background-image_jpg_repeat.fo
@@ -20,7 +20,7 @@
   <fo:layout-master-set>
     <fo:simple-page-master master-name="page"
       page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body background-image="../resources/images/bgimg72dpi.jpg"/>
+      <fo:region-body background-image="../../resources/images/bgimg72dpi.jpg"/>
     </fo:simple-page-master>
   </fo:layout-master-set>
   <fo:page-sequence master-reference="page">
diff --git a/test/accessibility/background-image_jpg_single.fo b/test/pdf/accessibility/background-image_jpg_single.fo
similarity index 95%
rename from test/accessibility/background-image_jpg_single.fo
rename to test/pdf/accessibility/background-image_jpg_single.fo
index 335353e..1efd10d 100644
--- a/test/accessibility/background-image_jpg_single.fo
+++ b/test/pdf/accessibility/background-image_jpg_single.fo
@@ -20,7 +20,7 @@
   <fo:layout-master-set>
     <fo:simple-page-master master-name="page"
       page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body background-image="../resources/images/bgimg72dpi.jpg" 
+      <fo:region-body background-image="../../resources/images/bgimg72dpi.jpg" 
         background-repeat="no-repeat" background-position-horizontal="50%" 
         background-position-vertical="50%"/>
     </fo:simple-page-master>
diff --git a/test/accessibility/background-image_jpg_repeat.fo b/test/pdf/accessibility/background-image_png_repeat.fo
similarity index 94%
copy from test/accessibility/background-image_jpg_repeat.fo
copy to test/pdf/accessibility/background-image_png_repeat.fo
index 727162e..9bbc3e4 100644
--- a/test/accessibility/background-image_jpg_repeat.fo
+++ b/test/pdf/accessibility/background-image_png_repeat.fo
@@ -20,7 +20,7 @@
   <fo:layout-master-set>
     <fo:simple-page-master master-name="page"
       page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body background-image="../resources/images/bgimg72dpi.jpg"/>
+      <fo:region-body background-image="../../resources/images/bgimg72dpi.png"/>
     </fo:simple-page-master>
   </fo:layout-master-set>
   <fo:page-sequence master-reference="page">
diff --git a/test/accessibility/background-image_png_single.fo b/test/pdf/accessibility/background-image_png_single.fo
similarity index 94%
rename from test/accessibility/background-image_png_single.fo
rename to test/pdf/accessibility/background-image_png_single.fo
index 90067ec..0cff427 100644
--- a/test/accessibility/background-image_png_single.fo
+++ b/test/pdf/accessibility/background-image_png_single.fo
@@ -20,7 +20,7 @@
   <fo:layout-master-set>
     <fo:simple-page-master master-name="page"
       page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body background-image="../resources/images/fop-logo-color-24bit.png" 
+      <fo:region-body background-image="../../resources/images/fop-logo-color-24bit.png" 
         background-repeat="no-repeat" background-position-horizontal="50%" 
         background-position-vertical="50%"/>
     </fo:simple-page-master>
diff --git a/test/accessibility/background-image_jpg_repeat.fo b/test/pdf/accessibility/background-image_svg_repeat.fo
similarity index 94%
rename from test/accessibility/background-image_jpg_repeat.fo
rename to test/pdf/accessibility/background-image_svg_repeat.fo
index 727162e..ba69947 100644
--- a/test/accessibility/background-image_jpg_repeat.fo
+++ b/test/pdf/accessibility/background-image_svg_repeat.fo
@@ -20,7 +20,7 @@
   <fo:layout-master-set>
     <fo:simple-page-master master-name="page"
       page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body background-image="../resources/images/bgimg72dpi.jpg"/>
+      <fo:region-body background-image="../../resources/images/rgb-circles.svg"/>
     </fo:simple-page-master>
   </fo:layout-master-set>
   <fo:page-sequence master-reference="page">
diff --git a/test/accessibility/background-image_svg_single.fo b/test/pdf/accessibility/background-image_svg_single.fo
similarity index 95%
rename from test/accessibility/background-image_svg_single.fo
rename to test/pdf/accessibility/background-image_svg_single.fo
index 3029f32..efe91c6 100644
--- a/test/accessibility/background-image_svg_single.fo
+++ b/test/pdf/accessibility/background-image_svg_single.fo
@@ -20,7 +20,7 @@
   <fo:layout-master-set>
     <fo:simple-page-master master-name="page"
       page-height="220pt" page-width="320pt" margin="10pt">
-      <fo:region-body background-image="../resources/images/rgb-circles.svg" 
+      <fo:region-body background-image="../../resources/images/rgb-circles.svg" 
         background-repeat="no-repeat" background-position-horizontal="50%" 
         background-position-vertical="50%"/>
     </fo:simple-page-master>
diff --git a/test/accessibility/complete.fo b/test/pdf/accessibility/complete.fo
similarity index 98%
copy from test/accessibility/complete.fo
copy to test/pdf/accessibility/complete.fo
index 7568475..23e8405 100644
--- a/test/accessibility/complete.fo
+++ b/test/pdf/accessibility/complete.fo
@@ -45,7 +45,7 @@
                   external-destination="http://xmlgraphics.apache.org/fop/">FOP 
                   website</fo:basic-link></fo:wrapper> for more 
               information</fo:block></fo:footnote-body></fo:footnote>. FOP has a nice logo: 
-        <fo:external-graphic src="../resources/images/fop-logo-color-24bit.png" 
+        <fo:external-graphic src="../../resources/images/fop-logo-color-24bit.png" 
           inline-progression-dimension.maximum="100%" content-width="scale-to-fit"
           fox:alt-text="FOP Logo"/></fo:block>
       <fo:table space-before="10pt" space-after="10pt" width="100%" table-layout="fixed">
diff --git a/test/accessibility/fop.xconf b/test/pdf/accessibility/fop.xconf
similarity index 91%
rename from test/accessibility/fop.xconf
rename to test/pdf/accessibility/fop.xconf
index 8c5dc2b..adfccd2 100644
--- a/test/accessibility/fop.xconf
+++ b/test/pdf/accessibility/fop.xconf
@@ -3,7 +3,7 @@
   <accessibility>true</accessibility>
   <source-resolution>144</source-resolution>
   <use-cache>false</use-cache>
-  <font-base>../resources/fonts/</font-base>
+  <font-base>../../resources/fonts/ttf/</font-base>
   <renderers>
     <renderer mime="application/pdf">
       <filterList>
diff --git a/test/accessibility/image_jpg.fo b/test/pdf/accessibility/image_jpg.fo
similarity index 96%
rename from test/accessibility/image_jpg.fo
rename to test/pdf/accessibility/image_jpg.fo
index 5fe36f6..2a2b600 100644
--- a/test/accessibility/image_jpg.fo
+++ b/test/pdf/accessibility/image_jpg.fo
@@ -27,7 +27,7 @@
   <fo:page-sequence master-reference="page">
     <fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify">
       <fo:block>This document contains an image in the JPEG format: <fo:external-graphic 
-          src="../resources/images/cmyk.jpg"
+          src="../../resources/images/cmyk.jpg"
           inline-progression-dimension.maximum="100%" content-width="scale-to-fit"
           fox:alt-text="CMYK colours"/>. Here is the end of the text.</fo:block>
     </fo:flow>
diff --git a/test/accessibility/image_png.fo b/test/pdf/accessibility/image_png.fo
similarity index 95%
rename from test/accessibility/image_png.fo
rename to test/pdf/accessibility/image_png.fo
index b529aa8..52ee80a 100644
--- a/test/accessibility/image_png.fo
+++ b/test/pdf/accessibility/image_png.fo
@@ -27,7 +27,7 @@
   <fo:page-sequence master-reference="page">
     <fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify">
       <fo:block>This document contains an image in the PNG format: <fo:external-graphic 
-          src="../resources/images/fop-logo-color-24bit.png"
+          src="../../resources/images/fop-logo-color-24bit.png"
           inline-progression-dimension.maximum="100%" content-width="scale-to-fit"
           fox:alt-text="FOP Logo"/>. Here is the end of the text.</fo:block>
     </fo:flow>
diff --git a/test/accessibility/image_svg.fo b/test/pdf/accessibility/image_svg.fo
similarity index 97%
rename from test/accessibility/image_svg.fo
rename to test/pdf/accessibility/image_svg.fo
index bbc77fe..96cfede 100644
--- a/test/accessibility/image_svg.fo
+++ b/test/pdf/accessibility/image_svg.fo
@@ -27,7 +27,7 @@
   <fo:page-sequence master-reference="page">
     <fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify">
       <fo:block>This document contains an image in the SVG format: <fo:external-graphic 
-          src="../resources/images/circles.svg"
+          src="../../resources/images/circles.svg"
           inline-progression-dimension.maximum="75pt" content-width="scale-to-fit"
           fox:alt-text="Nice circles"/>. And here is the same image as an instream-foreign-object: 
         <fo:instream-foreign-object inline-progression-dimension.maximum="75pt" 
diff --git a/test/accessibility/image_wmf.fo b/test/pdf/accessibility/image_wmf.fo
similarity index 96%
rename from test/accessibility/image_wmf.fo
rename to test/pdf/accessibility/image_wmf.fo
index 1a4de77..43112db 100644
--- a/test/accessibility/image_wmf.fo
+++ b/test/pdf/accessibility/image_wmf.fo
@@ -27,7 +27,7 @@
   <fo:page-sequence master-reference="page">
     <fo:flow flow-name="xsl-region-body" hyphenate="true" text-align="justify">
       <fo:block>This document contains an image in the WMF format: <fo:external-graphic 
-          src="../resources/images/testChart.wmf"
+          src="../../resources/images/testChart.wmf"
           inline-progression-dimension.maximum="100%" content-width="scale-to-fit"
           fox:alt-text="Metafile Companion Test Chart"/> Here is the end of the text.</fo:block>
     </fo:flow>
diff --git a/test/accessibility/leader.fo b/test/pdf/accessibility/leader.fo
similarity index 95%
rename from test/accessibility/leader.fo
rename to test/pdf/accessibility/leader.fo
index ffd7680..4b395cc 100644
--- a/test/accessibility/leader.fo
+++ b/test/pdf/accessibility/leader.fo
@@ -32,7 +32,7 @@
       <fo:block space-before="10pt">This is a text followed by a leader with 
         leader-pattern=​"use-content", the content being images:<fo:leader 
           leader-pattern="use-content"><fo:external-graphic 
-            src="../resources/images/list-item.png"/></fo:leader>1</fo:block>
+            src="../../resources/images/list-item.png"/></fo:leader>1</fo:block>
     </fo:flow>
   </fo:page-sequence>
 </fo:root>
diff --git a/test/accessibility/links.fo b/test/pdf/accessibility/links.fo
similarity index 100%
rename from test/accessibility/links.fo
rename to test/pdf/accessibility/links.fo
diff --git a/test/accessibility/pdf/background-image_jpg_repeat.pdf b/test/pdf/accessibility/pdf/background-image_jpg_repeat.pdf
similarity index 96%
rename from test/accessibility/pdf/background-image_jpg_repeat.pdf
rename to test/pdf/accessibility/pdf/background-image_jpg_repeat.pdf
index 2217617..9b4a7fc 100644
--- a/test/accessibility/pdf/background-image_jpg_repeat.pdf
+++ b/test/pdf/accessibility/pdf/background-image_jpg_repeat.pdf
Binary files differ
diff --git a/test/accessibility/pdf/background-image_jpg_single.pdf b/test/pdf/accessibility/pdf/background-image_jpg_single.pdf
similarity index 96%
rename from test/accessibility/pdf/background-image_jpg_single.pdf
rename to test/pdf/accessibility/pdf/background-image_jpg_single.pdf
index 232afdd..d1d0ecb 100644
--- a/test/accessibility/pdf/background-image_jpg_single.pdf
+++ b/test/pdf/accessibility/pdf/background-image_jpg_single.pdf
Binary files differ
diff --git a/test/accessibility/pdf/background-image_png_repeat.pdf b/test/pdf/accessibility/pdf/background-image_png_repeat.pdf
similarity index 98%
rename from test/accessibility/pdf/background-image_png_repeat.pdf
rename to test/pdf/accessibility/pdf/background-image_png_repeat.pdf
index 52da402..ccb2cf0 100644
--- a/test/accessibility/pdf/background-image_png_repeat.pdf
+++ b/test/pdf/accessibility/pdf/background-image_png_repeat.pdf
Binary files differ
diff --git a/test/pdf/accessibility/pdf/background-image_png_single.pdf b/test/pdf/accessibility/pdf/background-image_png_single.pdf
new file mode 100644
index 0000000..902520b
--- /dev/null
+++ b/test/pdf/accessibility/pdf/background-image_png_single.pdf
Binary files differ
diff --git a/test/accessibility/pdf/background-image_svg_repeat.pdf b/test/pdf/accessibility/pdf/background-image_svg_repeat.pdf
similarity index 96%
rename from test/accessibility/pdf/background-image_svg_repeat.pdf
rename to test/pdf/accessibility/pdf/background-image_svg_repeat.pdf
index 0bce240..a720a5b 100644
--- a/test/accessibility/pdf/background-image_svg_repeat.pdf
+++ b/test/pdf/accessibility/pdf/background-image_svg_repeat.pdf
Binary files differ
diff --git a/test/accessibility/pdf/background-image_svg_single.pdf b/test/pdf/accessibility/pdf/background-image_svg_single.pdf
similarity index 94%
rename from test/accessibility/pdf/background-image_svg_single.pdf
rename to test/pdf/accessibility/pdf/background-image_svg_single.pdf
index 3e4afcd..7e6e3e9 100644
--- a/test/accessibility/pdf/background-image_svg_single.pdf
+++ b/test/pdf/accessibility/pdf/background-image_svg_single.pdf
Binary files differ
diff --git a/test/pdf/accessibility/pdf/complete.pdf b/test/pdf/accessibility/pdf/complete.pdf
new file mode 100644
index 0000000..f1dc105
--- /dev/null
+++ b/test/pdf/accessibility/pdf/complete.pdf
Binary files differ
diff --git a/test/accessibility/pdf/image_jpg.pdf b/test/pdf/accessibility/pdf/image_jpg.pdf
similarity index 94%
rename from test/accessibility/pdf/image_jpg.pdf
rename to test/pdf/accessibility/pdf/image_jpg.pdf
index cb004bb..e943c83 100644
--- a/test/accessibility/pdf/image_jpg.pdf
+++ b/test/pdf/accessibility/pdf/image_jpg.pdf
Binary files differ
diff --git a/test/pdf/accessibility/pdf/image_png.pdf b/test/pdf/accessibility/pdf/image_png.pdf
new file mode 100644
index 0000000..5ea4467
--- /dev/null
+++ b/test/pdf/accessibility/pdf/image_png.pdf
Binary files differ
diff --git a/test/accessibility/pdf/image_svg.pdf b/test/pdf/accessibility/pdf/image_svg.pdf
similarity index 95%
rename from test/accessibility/pdf/image_svg.pdf
rename to test/pdf/accessibility/pdf/image_svg.pdf
index c3fce5b..db1e061 100644
--- a/test/accessibility/pdf/image_svg.pdf
+++ b/test/pdf/accessibility/pdf/image_svg.pdf
Binary files differ
diff --git a/test/pdf/accessibility/pdf/image_wmf.pdf b/test/pdf/accessibility/pdf/image_wmf.pdf
new file mode 100644
index 0000000..65c46d1
--- /dev/null
+++ b/test/pdf/accessibility/pdf/image_wmf.pdf
Binary files differ
diff --git a/test/accessibility/pdf/leader.pdf b/test/pdf/accessibility/pdf/leader.pdf
similarity index 94%
rename from test/accessibility/pdf/leader.pdf
rename to test/pdf/accessibility/pdf/leader.pdf
index c7432e7..d270c25 100644
--- a/test/accessibility/pdf/leader.pdf
+++ b/test/pdf/accessibility/pdf/leader.pdf
Binary files differ
diff --git a/test/accessibility/pdf/links.pdf b/test/pdf/accessibility/pdf/links.pdf
similarity index 94%
rename from test/accessibility/pdf/links.pdf
rename to test/pdf/accessibility/pdf/links.pdf
index 91d7c25..b2a5a42 100644
--- a/test/accessibility/pdf/links.pdf
+++ b/test/pdf/accessibility/pdf/links.pdf
Binary files differ
diff --git a/test/accessibility/pdf/role.pdf b/test/pdf/accessibility/pdf/role.pdf
similarity index 94%
rename from test/accessibility/pdf/role.pdf
rename to test/pdf/accessibility/pdf/role.pdf
index acb4350..329925b 100644
--- a/test/accessibility/pdf/role.pdf
+++ b/test/pdf/accessibility/pdf/role.pdf
Binary files differ
diff --git a/test/accessibility/pdf/role_non-standard.pdf b/test/pdf/accessibility/pdf/role_non-standard.pdf
similarity index 94%
rename from test/accessibility/pdf/role_non-standard.pdf
rename to test/pdf/accessibility/pdf/role_non-standard.pdf
index fcf614e..84daea7 100644
--- a/test/accessibility/pdf/role_non-standard.pdf
+++ b/test/pdf/accessibility/pdf/role_non-standard.pdf
Binary files differ
diff --git a/test/accessibility/pdf/text_1.pdf b/test/pdf/accessibility/pdf/text_1.pdf
similarity index 93%
rename from test/accessibility/pdf/text_1.pdf
rename to test/pdf/accessibility/pdf/text_1.pdf
index 596419c..13f0171 100644
--- a/test/accessibility/pdf/text_1.pdf
+++ b/test/pdf/accessibility/pdf/text_1.pdf
Binary files differ
diff --git a/test/accessibility/pdf/text_2.pdf b/test/pdf/accessibility/pdf/text_2.pdf
similarity index 95%
rename from test/accessibility/pdf/text_2.pdf
rename to test/pdf/accessibility/pdf/text_2.pdf
index 19fff21..9432639 100644
--- a/test/accessibility/pdf/text_2.pdf
+++ b/test/pdf/accessibility/pdf/text_2.pdf
Binary files differ
diff --git a/test/accessibility/pdf/text_font-embedding.pdf b/test/pdf/accessibility/pdf/text_font-embedding.pdf
similarity index 93%
rename from test/accessibility/pdf/text_font-embedding.pdf
rename to test/pdf/accessibility/pdf/text_font-embedding.pdf
index 0288449..7ffb40a 100644
--- a/test/accessibility/pdf/text_font-embedding.pdf
+++ b/test/pdf/accessibility/pdf/text_font-embedding.pdf
Binary files differ
diff --git a/test/accessibility/role.fo b/test/pdf/accessibility/role.fo
similarity index 100%
rename from test/accessibility/role.fo
rename to test/pdf/accessibility/role.fo
diff --git a/test/accessibility/role_non-standard.fo b/test/pdf/accessibility/role_non-standard.fo
similarity index 100%
rename from test/accessibility/role_non-standard.fo
rename to test/pdf/accessibility/role_non-standard.fo
diff --git a/test/accessibility/text_1.fo b/test/pdf/accessibility/text_1.fo
similarity index 100%
rename from test/accessibility/text_1.fo
rename to test/pdf/accessibility/text_1.fo
diff --git a/test/accessibility/text_2.fo b/test/pdf/accessibility/text_2.fo
similarity index 100%
rename from test/accessibility/text_2.fo
rename to test/pdf/accessibility/text_2.fo
diff --git a/test/accessibility/text_font-embedding.fo b/test/pdf/accessibility/text_font-embedding.fo
similarity index 100%
rename from test/accessibility/text_font-embedding.fo
rename to test/pdf/accessibility/text_font-embedding.fo