Index: test/layoutengine/standard-testcases/ps-extension_2.xml
===================================================================
--- test/layoutengine/standard-testcases/ps-extension_2.xml	(revision 0)
+++ test/layoutengine/standard-testcases/ps-extension_2.xml	(revision 0)
@@ -0,0 +1,100 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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: $ -->
+<testcase>
+  <info>
+    <p>
+      This test checks the PostScript extension for custom setup code. The extension attachments need to show
+      up in the area tree XML so the AreaTreeParser can fully restore the area tree.
+    </p>
+  </info>
+  <fo>
+  <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:ps="http://xmlgraphics.apache.org/fop/postscript">
+    <fo:layout-master-set>
+      <fo:simple-page-master master-name="template1" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm">
+        <fo:region-body margin-top="3cm"/>
+        <fo:region-before extent="3cm"/>
+        <fo:region-after extent="1.5cm"/>
+        <ps:ps-setpagedevice name="%FOPTestSetPageDevice: lower tray"><![CDATA[
+<<
+        /MediaPosition /4
+>>
+      ]]></ps:ps-setpagedevice>
+      </fo:simple-page-master>
+      <fo:simple-page-master master-name="template2" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm">
+        <fo:region-body margin-top="2cm"/>
+        <fo:region-before extent="2cm"/>
+        <fo:region-after extent="2.5cm"/>
+        <ps:ps-page-info>%FOPTestPageInfo: template2 page info</ps:ps-page-info>
+        <ps:ps-setpagedevice name="%FOPTestSetPageDevice: upper tray"><![CDATA[
+<<
+        /MediaPosition /1
+>>
+      ]]></ps:ps-setpagedevice>
+      </fo:simple-page-master>
+      <fo:simple-page-master master-name="template3" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm">
+        <fo:region-body margin-top="2cm"/>
+        <fo:region-before extent="2cm"/>
+        <fo:region-after extent="2.5cm"/>
+      </fo:simple-page-master>
+    </fo:layout-master-set>
+    <fo:declarations>
+      <ps:ps-title-line>%FOPTestTitleLine: test document title</ps:ps-title-line>
+      <ps:ps-page-info>%FOPTestPageInfo: test default page info</ps:ps-page-info>
+      <ps:ps-setpagedevice name="%FOPTestSetPageDevice: autofeed"><![CDATA[
+<<
+        /ManualFeed false
+>>
+    ]]></ps:ps-setpagedevice>
+    </fo:declarations>
+    <fo:page-sequence master-reference="template1">
+      <fo:flow flow-name="xsl-region-body">
+        <fo:block>Some text.</fo:block>
+      </fo:flow>
+    </fo:page-sequence>
+    <fo:page-sequence master-reference="template2">
+      <fo:flow flow-name="xsl-region-body">
+        <fo:block>Some more text.</fo:block>
+      </fo:flow>
+    </fo:page-sequence>
+    <fo:page-sequence master-reference="template3">
+      <fo:flow flow-name="xsl-region-body">
+        <fo:block>Even more text.</fo:block>
+      </fo:flow>
+    </fo:page-sequence>
+  </fo:root>
+  </fo>
+  <checks>
+    <eval expected="9" xpath="count(/areaTree/extension-attachments/child::*)"/>
+    <eval expected="%FOPTestTitleLine: test document title" xpath="/areaTree/extension-attachments/child::*[1]"/>
+    <eval expected="%FOPTestPageInfo: test default page info" xpath="/areaTree/extension-attachments/child::*[2]"/>
+    <eval expected="%FOPTestSetPageDevice: autofeed" xpath="/areaTree/extension-attachments/child::*[3]/@name"/>
+    <eval expected="%FOPTestTitleLine: test document title" xpath="/areaTree/extension-attachments/child::*[4]"/>
+    <eval expected="%FOPTestPageInfo: test default page info" xpath="/areaTree/extension-attachments/child::*[5]"/>
+    <eval expected="%FOPTestSetPageDevice: autofeed" xpath="/areaTree/extension-attachments/child::*[6]/@name"/>
+    <eval expected="%FOPTestTitleLine: test document title" xpath="/areaTree/extension-attachments/child::*[7]"/>
+    <eval expected="%FOPTestPageInfo: test default page info" xpath="/areaTree/extension-attachments/child::*[8]"/>
+    <eval expected="%FOPTestSetPageDevice: autofeed" xpath="/areaTree/extension-attachments/child::*[9]/@name"/>
+    <true xpath="contains(/areaTree/extension-attachments/child::*[2], '%FOPTestPageInfo: test default page info')"/>
+    <eval expected="1" xpath="count(/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*)"/>
+    <eval expected="%FOPTestSetPageDevice: lower tray" xpath="/areaTree/pageSequence/pageViewport[@nr=1]/page/extension-attachments/child::*[1]/@name"/>
+    <eval expected="2" xpath="count(/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*)"/>
+    <eval expected="%FOPTestPageInfo: template2 page info" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[1]"/>
+    <eval expected="%FOPTestSetPageDevice: upper tray" xpath="/areaTree/pageSequence/pageViewport[@nr=2]/page/extension-attachments/child::*[2]/@name"/>
+  </checks>
+</testcase>
Index: src/java/org/apache/fop/render/ps/objects/PSInteger.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSInteger.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSInteger.java	(revision 0)
@@ -0,0 +1,42 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript integer object
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSInteger extends PSSimpleObject {
+
+    static class Maker extends PSSimpleObject.Maker {
+        /** @see org.apache.fop.render.ps.objects.PSSimpleObjectMaker#createPSObject() */
+        protected PSObject createPSObject() {
+            return new PSInteger();
+        }
+    }
+
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        if (obj instanceof PSInteger) {
+            return super.equals(obj);
+        }
+        return false;
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSPoliciesDictionary.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSPoliciesDictionary.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSPoliciesDictionary.java	(revision 0)
@@ -0,0 +1,42 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript special policies dictionary
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSPoliciesDictionary extends PSDictionary {
+
+    public static class Maker extends PSDictionary.Maker {
+
+        /** @see org.apache.fop.render.ps.objects#PSDictionaryMaker#init() */
+        public void init() {
+            // policies dictionary
+            super.makerMap = new java.util.HashMap() {
+                {
+                    put("/PolicyNotFound", PSDictionary.Maker.integerMaker);
+                    put("/PageSize", PSDictionary.Maker.integerMaker);
+                    put("/PolicyReport", PSDictionary.Maker.procedureMaker);
+                }
+            };
+        }
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSComplexObject.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSComplexObject.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSComplexObject.java	(revision 0)
@@ -0,0 +1,157 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript complex data type
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSComplexObject {
+
+    /**
+     * Maker of complex objects
+     * 
+     * @author Adrian Cumiskey
+     */
+    public abstract static class Maker extends PSSimpleObject.Maker {
+
+        /** opening brace index */
+        protected static final int OPENING = 0;
+
+        /** closing brace index */
+        protected static final int CLOSING = 1;
+
+        /**
+         * Simple token index holding class
+         */
+        protected static class Token {
+            public int beginIndex = -1, endIndex = -1;
+        }
+
+        /**
+         * Returns a Token containing the begin and end index of the next token
+         * found in a given string
+         * 
+         * @param str
+         *            string to search
+         * @param fromIndex
+         *            search from index
+         * @return Token containing the begin and end index of the next token
+         */
+        protected static Token nextToken(String str, int fromIndex) {
+            boolean beginFound = false, endFound = false;
+            Token t = null;
+            for (int i = fromIndex; i < str.length(); i++) {
+                if (!beginFound && !Character.isWhitespace(str.charAt(i))) {
+                    t = new Token();
+                    t.beginIndex = i;
+                    beginFound = true;
+                } else if (Character.isWhitespace(str.charAt(i))) {
+                    if (beginFound) {
+                        endFound = true;
+                        t.endIndex = i;
+                        break;
+                    }
+                }
+            }
+            if (beginFound && !endFound) {
+                t.endIndex = str.length();
+            }
+            return t;
+        }
+
+        /**
+         * Returns the closing brace index from a given string searches from a
+         * given index
+         * 
+         * @param str
+         *            string to search
+         * @param braces
+         *            string array of opening and closing brace
+         * @param fromIndex
+         *            searches from index
+         * @return matching brace index
+         * @throws PSObjectException exception
+         */
+        protected static int indexOfMatchingBrace(String str, String[] braces,
+                int fromIndex) throws PSObjectException {
+            final int len = str.length();
+            if (braces.length != 2) {
+                throw new PSObjectException("Wrong number of braces");
+            }
+            for (int index = fromIndex, openCnt = 0, closeCnt = 0; index < len; index++) {
+                if (str.startsWith(braces[OPENING], index)) {
+                    openCnt++;
+                } else if (str.startsWith(braces[CLOSING], index)) {
+                    closeCnt++;
+                    if (openCnt > 0 && openCnt == closeCnt) {
+                        return index; // found!
+                    }
+                }
+            }
+            return -1; // not found :-(
+        }
+
+        /**
+         * Strips braces from complex object string
+         * 
+         * @param str
+         *            string
+         * @return string with braces stripped
+         * @throws PSObjectException exception
+         */
+        protected String stripBraces(String str) throws PSObjectException {
+            String[] braces = getBraces();
+
+            // find first opening brace
+            int firstIndex = str.indexOf(braces[OPENING]);
+            if (firstIndex == -1) {
+                throw new PSObjectException(
+                        "Failed to find opening parameter '" + braces[OPENING]
+                                + "");
+            }
+
+            // find last matching brace
+            int lastIndex = indexOfMatchingBrace(str, braces, firstIndex);
+            if (lastIndex == -1) {
+                throw new PSObjectException(
+                        "Failed to find matching closing parameter '"
+                                + braces[CLOSING] + "'");
+            }
+
+            // strip brace and trim
+            int braceLen = getBraceLength();
+            str = str.substring(firstIndex + braceLen, lastIndex).trim();
+            return str;
+        }
+
+        /**
+         * @return open/close brace string array
+         */
+        public abstract String[] getBraces();
+
+        /**
+         * @return brace length
+         */
+        public int getBraceLength() {
+            return getBraces()[0].length();
+        }
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSBoolean.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSBoolean.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSBoolean.java	(revision 0)
@@ -0,0 +1,47 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript boolean object
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSBoolean extends PSSimpleObject {
+
+    static class Maker extends PSSimpleObject.Maker {
+        /** @see org.apache.fop.render.ps.objects.PSSimpleObjectMaker#createPSObject() */
+        protected PSObject createPSObject() {
+            return new PSBoolean();
+        }
+    }
+
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        if (obj instanceof PSBoolean) {
+            return super.equals(obj);
+        }
+        return false;
+    }
+
+    /** @see java.lang.Object#hashCode() */
+    public int hashCode() {
+        return "true".equals(value) ? 1 : 0;
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSSimpleObject.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSSimpleObject.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSSimpleObject.java	(revision 0)
@@ -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.render.ps.objects;
+
+/**
+ * postscript simple base object
+ * 
+ * @author Adrian Cumiskey
+ */
+public abstract class PSSimpleObject extends PSObject {
+    
+    public static abstract class Maker extends PSObject.Maker {
+        
+        /** @see org.apache.fop.render.ps.objects.PSObjectMaker#make(String) */
+        public PSObject make(String str) throws PSObjectException {
+            str = str.trim();
+            PSObject obj = createPSObject();
+            if (obj instanceof PSSimpleObject) {
+                PSSimpleObject simpleObj = (PSSimpleObject)obj;
+                simpleObj.setValue(str);
+            }
+            return obj;
+        }
+    }
+    
+    /**
+     * object value
+     */
+    protected Object value = null;
+    
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        if (this.toString().equals(obj.toString())) {
+            return true;
+        }
+        return false;
+    }
+    
+    /** @see java.lang.Object#hashCode() */
+    public int hashCode() {
+        return value.hashCode();
+    }
+
+    /**
+     * sets the objects primitive value
+     * @param value
+     */
+    public void setValue(Object value) {
+        this.value = value;
+    }
+
+    /**
+     * Returns the objects primitive value
+     * @param value
+     */
+    public Object getValue() {
+        return value;
+    }
+    
+    /** @see java.lang.String#toString() */
+    public String toString() {
+        return ""+value;
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/objects/PSNullObject.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSNullObject.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSNullObject.java	(revision 0)
@@ -0,0 +1,44 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript null object
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSNullObject extends PSObject {
+
+    static class Maker extends PSSimpleObject.Maker {
+        /** @see org.apache.fop.render.ps.objects.PSSimpleObjectMaker#createPSObject() */
+        protected PSObject createPSObject() {
+            return new PSNullObject();
+        }
+    }
+
+    /** @see java.lang.String#toString() */
+    public String toString() {
+        return "null";
+    }
+
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        return obj instanceof PSNullObject;
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSDictionary.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSDictionary.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSDictionary.java	(revision 0)
@@ -0,0 +1,414 @@
+/*
+ * 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.render.ps.objects;
+
+import java.io.IOException;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * encapsulates postscript dictionary object and the child objects which may be
+ * contained within it. Dictionary remembers what content has already been
+ * output. This is necessary to avoid unnecessary postscript setpagedevice calls
+ * which in turn invokes device specific initgraphics calls which can result in
+ * a blank page being produced.
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSDictionary extends PSObject {
+
+    /**
+     * 
+     * postcript setpagedevice dictionary parser and maker of postscript
+     * dictionary objects
+     * 
+     * @author Adrian Cumiskey
+     * 
+     */
+    public static class Maker extends PSComplexObject.Maker {
+
+        /**
+         * braces
+         */
+        protected static final String[] BRACES = new String[] {"<<", ">>"};
+
+        /** integer maker */
+        protected static PSInteger.Maker integerMaker;
+
+        /** procedure maker */
+        protected static PSProcedure.Maker procedureMaker;
+
+        /** null object maker */
+        protected static PSNullObject.Maker nullMaker;
+
+        /** @see org.apache.fop.render.ps.objects.PSComplexObjectMaker#getBraces() */
+        public String[] getBraces() {
+            return BRACES;
+        }
+
+        /**
+         * mapping of page device parameter name to type
+         */
+        protected Map makerMap;
+
+        /**
+         * list postscript keys with valid null object values
+         */
+        private static List nullableObjects;
+
+        /**
+         * dictionary maker is a singleton
+         */
+        private static Maker instance = null;
+
+        /**
+         * Singleton instance accessor method
+         * 
+         * @return instance of dictionary maker
+         */
+        public static Maker getInstance() {
+            if (instance == null) {
+                instance = new Maker();
+            }
+            return instance;
+        }
+
+        /**
+         * initializes postscript object maker
+         */
+        protected void init() {
+            final PSArrayObject.Maker arrayMaker = new PSArrayObject.Maker();
+            final PSBoolean.Maker booleanMaker = new PSBoolean.Maker();
+            final PSNumber.Maker numberMaker = new PSNumber.Maker();
+            final PSString.Maker stringMaker = new PSString.Maker();
+            final PSDictionary.Maker dictionaryMaker = this;
+            final PSPoliciesDictionary.Maker policiesMaker = new PSPoliciesDictionary.Maker();
+
+            this.makerMap = new java.util.HashMap() {
+                {
+                    // media selection parameters
+                    put("/InsertSheet", booleanMaker);
+                    put("/ManualFeed", booleanMaker);
+                    put("/TraySwitch", booleanMaker);
+                    put("/DeferredMediaSelection", booleanMaker);
+                    put("/MediaColor", stringMaker);
+                    put("/MediaWeight", numberMaker);
+                    put("/MediaType", stringMaker);
+                    put("/MediaClass", stringMaker);
+                    put("/MediaPosition", integerMaker);
+                    put("/LeadingEdge", integerMaker);
+                    put("/MediaPosition", integerMaker);
+                    put("/PageSize", arrayMaker);
+                    put("/InputAttributes", dictionaryMaker);
+
+                    // roll-fed media
+                    put("/AdvanceDistance", integerMaker);
+                    put("/Orientation", integerMaker);
+                    put("/AdvanceMedia", integerMaker);
+                    put("/RollFedMedia", booleanMaker);
+                    put("/CutMedia", integerMaker);
+
+                    // page image placement
+                    put("/Duplex", booleanMaker);
+                    put("/MirrorPrint", booleanMaker);
+                    put("/HWResolution", arrayMaker);
+                    put("/NegativePrint", booleanMaker);
+                    put("/ImageShift", arrayMaker);
+                    put("/PageOffset", arrayMaker);
+                    put("/ImagingBBox", arrayMaker);
+                    put("/Tumble", booleanMaker);
+                    put("/Margins", arrayMaker);
+
+                    // page delivery
+                    put("/Collate", booleanMaker);
+                    put("/OutputDevice", stringMaker);
+                    put("/Jog", integerMaker);
+                    put("/OutputFaceUp", booleanMaker);
+                    put("/NumCopies", integerMaker);
+                    put("/OutputType", stringMaker);
+                    put("/OutputAttributes", dictionaryMaker);
+
+                    // color support
+                    put("/MaxSeparations", integerMaker);
+                    put("/Separations", booleanMaker);
+                    put("/PageDeviceName", stringMaker);
+                    put("/Trapping", booleanMaker);
+                    put("/ProcessColorModel", stringMaker);
+                    put("/TrappingDetails", dictionaryMaker);
+                    put("/SeparationColorNames", arrayMaker);
+                    put("/UseCIEColor", booleanMaker);
+                    put("/SeparationOrder", arrayMaker);
+
+                    // device initialization and page setup
+                    put("/BeginPage", procedureMaker);
+                    put("/EndPage", procedureMaker);
+                    put("/Install", procedureMaker);
+
+                    // Unsatisfied parameter requests
+                    put("/Policies", policiesMaker);
+                }
+            };
+
+            nullableObjects = new java.util.ArrayList() {
+                {
+                    add("/MediaColor");
+                    add("/MediaWeight");
+                    add("/MediaType");
+                    add("/MediaClass");
+                    add("/MediaPosition");
+                    add("/LeadingEdge");
+                    add("/InputAttributes");
+                    add("/ImagingBBox");
+                    add("/NumCopies");
+                    add("/OutputType");
+                    add("/PageDeviceName");
+                }
+            };
+        }
+
+        /**
+         * default private constructor which initializes the page device
+         * parameter map
+         */
+        protected Maker() {
+            integerMaker = new PSInteger.Maker();
+            procedureMaker = new PSProcedure.Maker();
+            nullMaker = new PSNullObject.Maker();
+            init();
+        }
+
+        /** @see org.apache.fop.render.ps.objects.PSObjectMaker#createPSObject() */
+        protected PSObject createPSObject() {
+            return new PSDictionary();
+        }
+
+        /** @see org.apache.fop.render.ps.objects.PSObjectMaker#make(String) */
+        public PSObject make(String str) throws PSObjectException {
+            str = stripBraces(str.trim());
+
+            // length of dictionary string
+            final int len = str.length();
+
+            // create our dictionary
+            PSDictionary dictionary = new PSDictionary();
+
+            Token keyToken;
+            for (int currIndex = 0; (keyToken = nextToken(str, currIndex)) != null
+                    && currIndex <= len;) {
+                String key = str.substring(keyToken.beginIndex,
+                        keyToken.endIndex);
+                if (key == null) {
+                    throw new PSObjectException("Failed to parse object key");
+                }
+                PSObject obj = null;
+                Token valueToken = nextToken(str, keyToken.endIndex + 1);
+                PSObject.Maker maker;
+                if (nullableObjects.contains(key)
+                        && "null".equals(str.substring(valueToken.beginIndex,
+                                valueToken.endIndex))) {
+                    maker = nullMaker;
+                } else {
+                    if (makerMap.containsKey(key)) {
+                        maker = (PSObject.Maker) makerMap.get(key);
+                    } else {
+                        // dictionary number/index mapping (i.e.
+                        // /InputAttributes
+                        // for tray number)
+                        try {
+                            Integer.parseInt(key);
+                            maker = this;
+                        } catch (NumberFormatException e) {
+                            throw new PSObjectException("Unrecognised key '"
+                                    + key + "'");
+                        }
+                    }
+                    if (maker instanceof PSComplexObject.Maker) {
+                        PSComplexObject.Maker complexMaker = (PSComplexObject.Maker) maker;
+                        String[] braces = complexMaker.getBraces();
+                        // find opening brace
+                        valueToken.beginIndex = str.indexOf(braces[OPENING],
+                                keyToken.endIndex + 1);
+                        if (valueToken.beginIndex < 0) {
+                            throw new PSObjectException("Opening value brace '"
+                                    + braces[OPENING] + "' not found for key '"
+                                    + key + "'");
+                        }
+                        // find closing brace
+                        valueToken.endIndex = indexOfMatchingBrace(str, braces,
+                                valueToken.beginIndex)
+                                + complexMaker.getBraceLength();
+                        if (valueToken.endIndex < 0) {
+                            throw new PSObjectException("Closing value brace '"
+                                    + braces[CLOSING] + "' not found for key '"
+                                    + key + "'");
+                        }
+                    }
+                }
+                String value = str.substring(valueToken.beginIndex,
+                        valueToken.endIndex);
+                obj = maker.make(value);
+                if (obj == null) {
+                    throw new PSObjectException("Failed to parse object for '"
+                            + key + "'");
+                }
+                dictionary.put(key, obj);
+                currIndex = valueToken.endIndex + 1;
+            }
+            return dictionary;
+        }
+    }
+
+    // dictionary content
+    private Map contentMap = new java.util.HashMap();
+
+    // dictionary content that has not been output/written yet
+    private Map unWrittenContentMap = new java.util.HashMap();
+
+    /**
+     * Returns dictionary string representation of a given map
+     * 
+     * @param map
+     * @return
+     */
+    private static String write(Map map) {
+        if (map.isEmpty()) {
+            return "";
+        }
+        StringBuffer sb = new StringBuffer("<<");
+        for (Iterator it = map.keySet().iterator(); it.hasNext();) {
+            String key = (String) it.next();
+            sb.append("\n" + key + " ");
+            Object obj = map.get(key);
+            sb.append(obj.toString());
+        }
+        sb.append("\n>>");
+        return sb.toString();
+    }
+
+    /**
+     * @return boolean if dictionary has unwritten changes
+     */
+    public boolean hasUnwrittenContent() {
+        return unWrittenContentMap.size() > 0;
+    }
+
+    /**
+     * Returns a dictionary string with containing all unwritten content note:
+     * unnecessary writes are important as there is a device specific
+     * initgraphics call by the underlying poscript interpreter on every
+     * setpagedevice call which can result in blank pages etc.
+     * 
+     * @return unwritten content dictionary string
+     * @throws IOException
+     *             io exception
+     */
+    public String getUnWrittenContent() throws IOException {
+        String unwrittenContent = write(unWrittenContentMap);
+        unWrittenContentMap.clear();
+        return unwrittenContent;
+    }
+
+    /**
+     * @see java.util.Map#put(Object, Object)
+     */
+    public void put(Object key, Object value) {
+        if (!contentMap.containsKey(key)) {
+            contentMap.put(key, value);
+            unWrittenContentMap.put(key, value);
+            return;
+        }
+        Object currValue = contentMap.get(key);
+        if (!value.equals(currValue)) {
+            unWrittenContentMap.put(key, value);
+            contentMap.put(key, value);
+        }
+    }
+
+    /**
+     * @see java.util.Map#containsKey(Object)
+     */
+    public boolean containsKey(String key) {
+        return contentMap.containsKey(key);
+    }
+
+    /**
+     * @see java.util.Map#containsValue(Object)
+     */
+    public boolean containsValue(PSObject obj) {
+        return contentMap.containsValue(obj);
+    }
+
+    /** @see java.util.Map#clear() */
+    public void clear() {
+        contentMap.clear();
+        unWrittenContentMap.clear();
+    }
+
+    /** @see java.lang.String#toString() */
+    public String toString() {
+        return write(contentMap);
+    }
+
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        if (!(obj instanceof PSDictionary)) {
+            return false;
+        }
+        PSDictionary dictionaryObj = (PSDictionary) obj;
+        if (dictionaryObj.size() != size()) {
+            return false;
+        }
+        for (Iterator it = contentMap.keySet().iterator(); it.hasNext();) {
+            String key = (String) it.next();
+            if (!dictionaryObj.containsKey(key)) {
+                return false;
+            }
+            if (!dictionaryObj.get(key).equals(get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** @see java.util.Map#get(Object) */
+    public void putAll(PSDictionary dictionary) {
+        for (Iterator it = dictionary.keySet().iterator(); it.hasNext();) {
+            Object key = it.next();
+            Object value = dictionary.get(key);
+            put(key, value);
+        }
+    }
+
+    /** @see java.util.Map#keySet(Object) */
+    public Set keySet() {
+        return contentMap.keySet();
+    }
+
+    /** @see java.util.Map#get(Object) */
+    public Object get(Object key) {
+        return contentMap.get(key);
+    }
+
+    /** @see java.util.Map#size() */
+    public int size() {
+        return contentMap.size();
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/objects/PSObjectException.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSObjectException.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSObjectException.java	(revision 0)
@@ -0,0 +1,35 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript object exception
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSObjectException extends Exception {
+
+    /**
+     * default constructor
+     * @param string exception error string
+     */
+    public PSObjectException(String string) {
+        super(string);
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSArrayObject.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSArrayObject.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSArrayObject.java	(revision 0)
@@ -0,0 +1,115 @@
+/*
+ * 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.render.ps.objects;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * postscript array object
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSArrayObject extends PSObject {
+
+    static class Maker extends PSComplexObject.Maker {
+        /**
+         * braces
+         */
+        static final String[] BRACES = { "[", "]" };
+
+        public PSObject make(String str) throws PSObjectException {
+            str = stripBraces(str);
+            PSArrayObject arrayObject = (PSArrayObject) createPSObject();
+            StringTokenizer st = new StringTokenizer(str);
+            while (st.hasMoreTokens()) {
+                arrayObject.add(st.nextToken());
+            }
+            return arrayObject;
+        }
+
+        /** @see org.apache.fop.render.ps.objects.PSComplexObjectMaker#getBraces() */
+        public String[] getBraces() {
+            return BRACES;
+        }
+
+        /** @see org.apache.fop.render.ps.objects.PSObjectMaker#createPSObject() */
+        protected PSObject createPSObject() {
+            return new PSArrayObject();
+        }
+    }
+
+    private List list = new java.util.ArrayList();
+
+    /** @see java.lang.String#toString() */
+    public String toString() {
+        String str = "[";
+        for (int i = 0; i < list.size(); i++) {
+            Object element = list.get(i);
+            str += element + " ";
+        }
+        str = str.trim();
+        str += "]";
+        return str;
+    }
+
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        if (obj instanceof PSArrayObject) {
+            PSArrayObject arrayObj = (PSArrayObject) obj;
+            if (list.size() != arrayObj.size()) {
+                return false;
+            }
+            final int len = list.size();
+            for (int i = 0; i < len; i++) {
+                if (!list.get(i).equals(arrayObj.get(i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /** @see java.lang.Object#hashCode() */
+    public int hashCode() {
+        int hashCode = 1;
+        for (Iterator iter = list.iterator(); iter.hasNext();) {
+            Object element = (Object) iter.next();
+            hashCode = hashCode * 31 + element.hashCode();
+        }
+        return hashCode;
+    }
+
+    /** @see java.util.List#get(int) */
+    public Object get(int index) {
+        return list.get(index);
+    }
+
+    /** @see java.util.List#add(Object) */
+    public void add(Object value) {
+        list.add(value);
+    }
+
+    /** @see java.util.List#size() */
+    private int size() {
+        return list.size();
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/objects/PSObject.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSObject.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSObject.java	(revision 0)
@@ -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.render.ps.objects;
+
+/**
+ * postscript object base class
+ * 
+ * @author Adrian Cumiskey
+ */
+public abstract class PSObject {
+
+    public abstract static class Maker {
+
+        /**
+         * Makes a postscript object
+         * 
+         * @param string string
+         * @return dictionary object
+         * @throws PSObjectException exception
+         */
+        public abstract PSObject make(String string) throws PSObjectException;
+
+        /**
+         * creates a new object instance
+         * 
+         * @return new postscript object
+         */
+        protected abstract PSObject createPSObject();
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSString.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSString.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSString.java	(revision 0)
@@ -0,0 +1,42 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript string object
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSString extends PSSimpleObject {
+    
+    static class Maker extends PSSimpleObject.Maker {
+        /** @see org.apache.fop.render.ps.objects.PSSimpleObjectMaker#createPSObject() */
+        protected PSObject createPSObject() {
+            return new PSString();
+        }        
+    }
+
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        if (obj instanceof PSString) {
+            return super.equals(obj);
+        }
+        return false;
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSProcedure.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSProcedure.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSProcedure.java	(revision 0)
@@ -0,0 +1,52 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript procedure
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSProcedure extends PSSimpleObject {
+
+    static class Maker extends PSComplexObject.Maker {
+        /**
+         * braces
+         */
+        static final String[] BRACES = {"{", "}"};
+
+        /** @see org.apache.fop.render.ps.objects.PSComplexObjectMaker#getBraces() */
+        public String[] getBraces() {
+            return BRACES;
+        }
+
+        /** @see org.apache.fop.render.ps.objects.PSObjectMaker#createPSObject() */
+        protected PSObject createPSObject() {
+            return new PSProcedure();
+        }
+    }
+
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        if (obj instanceof PSProcedure) {
+            return super.equals(obj);
+        }
+        return false;
+    }
+}
Index: src/java/org/apache/fop/render/ps/objects/PSNumber.java
===================================================================
--- src/java/org/apache/fop/render/ps/objects/PSNumber.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/objects/PSNumber.java	(revision 0)
@@ -0,0 +1,42 @@
+/*
+ * 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.render.ps.objects;
+
+/**
+ * postscript number object
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSNumber extends PSSimpleObject {
+
+    static class Maker extends PSSimpleObject.Maker {
+        /** @see org.apache.fop.render.ps.objects.PSSimpleObjectMaker#createPSObject() */
+        protected PSObject createPSObject() {
+            return new PSNumber();
+        }
+    }
+
+    /** @see java.lang.Object#equals(Object) */
+    public boolean equals(Object obj) {
+        if (obj instanceof PSNumber) {
+            return super.equals(obj);
+        }
+        return false;
+    }
+}
Index: src/java/org/apache/fop/render/ps/PSRenderer.java
===================================================================
--- src/java/org/apache/fop/render/ps/PSRenderer.java	(revision 529569)
+++ src/java/org/apache/fop/render/ps/PSRenderer.java	(working copy)
@@ -28,6 +28,7 @@
 import java.io.InputStream;
 import java.io.LineNumberReader;
 import java.io.OutputStream;
+import java.util.Collection;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -72,7 +73,15 @@
 import org.apache.fop.render.AbstractPathOrientedRenderer;
 import org.apache.fop.render.ImageAdapter;
 import org.apache.fop.render.RendererContext;
+import org.apache.fop.render.ps.extensions.PSExtensionAttachment;
+import org.apache.fop.render.ps.extensions.PSPageInfo;
+import org.apache.fop.render.ps.extensions.PSSetPageDevice;
 import org.apache.fop.render.ps.extensions.PSSetupCode;
+import org.apache.fop.render.ps.extensions.PSTitleLine;
+import org.apache.fop.render.ps.objects.PSArrayObject;
+import org.apache.fop.render.ps.objects.PSDictionary;
+import org.apache.fop.render.ps.objects.PSNullObject;
+import org.apache.fop.render.ps.objects.PSObjectException;
 import org.apache.fop.util.CharUtilities;
 
 import org.apache.xmlgraphics.ps.DSCConstants;
@@ -124,27 +133,47 @@
     private int languageLevel = PSGenerator.DEFAULT_LANGUAGE_LEVEL;
 
     /** the OutputStream the PS file is written to */
-    private OutputStream outputStream;
+    private OutputStream outputStream = null;
+
     /** the temporary file in case of two-pass processing */
-    private File tempFile;
+    private File tempFile = null;
     
     /** The PostScript generator used to output the PostScript */
-    protected PSGenerator gen;
+    protected PSGenerator gen = null;
+    
     /** Determines whether the PS file is generated in two passes to minimize file size */
     private boolean twoPassGeneration = false;
     private boolean ioTrouble = false;
-
     private boolean inTextMode = false;
-    private boolean firstPageSequenceReceived = false;
+    private boolean firstPageInSequence = false;
+    
+    /** current page setup (page master postscript layout */
+    private PSPageSetup currentPageSetup = null;
+    
+    /** page setup map - caches page master postscript layout */
+    private Map pageSetupMap = null; 
 
-    /** Used to temporarily store PSSetupCode instance until they can be written. */
-    private List setupCodeList;
+    /** encapsulation of dictionary used in setpagedevice instruction **/
+    private PSDictionary pageDeviceDictionary = new PSDictionary();
 
+    /** Used to temporarily store ps extension attachments until they can be written. */
+    private List setupCodeList = null;
+
     /** This is a map of PSResource instances of all fonts defined (key: font key) */
-    private Map fontResources;
+    private Map fontResources = null;
+    
     /** This is a map of PSResource instances of all forms (key: uri) */
-    private Map formResources;
+    private Map formResources = null;
+
+    /** title of document **/
+    private String dscTitle = null;
+
+    /** fop default page info **/
+    private String defaultPageInfo = null;
     
+    /** fop current page info **/
+    private String currentPageInfo = null;
+
     /**
      * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
      */
@@ -183,7 +212,12 @@
         }
     }
 
-    private boolean booleanValueOf(Object obj) {
+    /**
+     * Returns boolean value of given object
+     * @param obj object
+     * @return boolean valie
+     */
+    private static boolean booleanValueOf(Object obj) {
         if (obj instanceof Boolean) {
             return ((Boolean)obj).booleanValue();
         } else if (obj instanceof String) {
@@ -193,7 +227,12 @@
         }
     }
     
-    private int intValueOf(Object obj) {
+    /**
+     * Returns integer value of given object
+     * @param obj object
+     * @return integer valie
+     */
+    private static int intValueOf(Object obj) {
         if (obj instanceof Integer) {
             return ((Integer)obj).intValue();
         } else if (obj instanceof String) {
@@ -391,6 +430,12 @@
         }
     }
 
+    /**
+     * TODO: provide comment
+     * @param uri
+     * @param fopimage
+     * @return
+     */
     protected PSResource getFormForImage(String uri, FopImage fopimage) {
         if (this.formResources == null) {
             this.formResources = new java.util.HashMap();
@@ -402,7 +447,13 @@
         }
         return form;
     }
-    
+
+    /**
+     * Determines if the image is inlined
+     * @param uri
+     * @param image
+     * @return
+     */
     protected boolean isImageInlined(String uri, FopImage image) {
         return !this.twoPassGeneration;
     }
@@ -486,6 +537,11 @@
         }
     }
 
+    /**
+     * TODO: provide comment
+     * @param key
+     * @return
+     */
     private String getPostScriptNameForFontKey(String key) {
         Map fonts = fontInfo.getFonts();
         Typeface tf = (Typeface)fonts.get(key);
@@ -533,6 +589,11 @@
         }
     }
 
+    /**
+     * TODO: provide comment
+     * @param col
+     * @throws IOException
+     */
     private void useColor(Color col) throws IOException {
         gen.useRGBColor(col);
     }
@@ -708,6 +769,20 @@
     }
     
     /**
+     * returns default ps generator object
+     * @param out output stream
+     * @return ps generator
+     */
+    protected PSGenerator getPSGenerator(OutputStream out) {
+        return new PSGenerator(out) {
+            /** Need to subclass PSRenderer to have better URI resolution */
+            public Source resolveURI(String uri) {
+                return userAgent.resolveURI(uri);
+            }
+        };
+    }
+    
+    /**
      * @see org.apache.fop.render.Renderer#startRenderer(OutputStream)
      */
     public void startRenderer(OutputStream outputStream)
@@ -725,29 +800,13 @@
         }
         
         //Setup for PostScript generation
-        this.gen = new PSGenerator(out) {
-            /** Need to subclass PSGenerator to have better URI resolution */
-            public Source resolveURI(String uri) {
-                return userAgent.resolveURI(uri);
-            }
-        };
+        this.gen = getPSGenerator(out);
         this.gen.setPSLevel(this.languageLevel);
         this.currentPageNumber = 0;
 
-        //PostScript Header
-        writeln(DSCConstants.PS_ADOBE_30);
-        gen.writeDSCComment(DSCConstants.CREATOR, new String[] {userAgent.getProducer()});
-        gen.writeDSCComment(DSCConstants.CREATION_DATE, new Object[] {new java.util.Date()});
-        gen.writeDSCComment(DSCConstants.LANGUAGE_LEVEL, new Integer(gen.getPSLevel()));
-        gen.writeDSCComment(DSCConstants.PAGES, new Object[] {DSCConstants.ATEND});
-        gen.writeDSCComment(DSCConstants.DOCUMENT_SUPPLIED_RESOURCES, 
-                new Object[] {DSCConstants.ATEND});
-        gen.writeDSCComment(DSCConstants.END_COMMENTS);
-
-        //Defaults
-        gen.writeDSCComment(DSCConstants.BEGIN_DEFAULTS);
-        gen.writeDSCComment(DSCConstants.END_DEFAULTS);
-
+        //Initial page device dictionary
+        pageDeviceDictionary.put("/ImagingBBox", new PSNullObject() );
+        pageSetupMap = new java.util.HashMap();
         //Prolog and Setup written right before the first page-sequence, see startPageSequence()
     }
 
@@ -776,6 +835,8 @@
             IOUtils.closeQuietly(gen.getOutputStream());
             rewritePostScriptFile();
         }
+        pageDeviceDictionary.clear();
+        pageSetupMap.clear();
     }
     
     /**
@@ -809,173 +870,331 @@
             log.debug("Resource Processing complete in " + duration + " ms.");
         }
     }
-
+    
+    /**
+     * processes an extension attachment
+     * 
+     * @param attachment
+     */
+    private void processAttachment(ExtensionAttachment attachment) {    
+        if (log.isDebugEnabled()) {
+            log.debug("Processing ExtensionAttachment: " + attachment);
+        }
+        if (PSExtensionAttachment.CATEGORY.equals(attachment.getCategory())) {
+            if (attachment instanceof PSTitleLine) {
+                PSTitleLine titleLine = (PSTitleLine)attachment;
+                dscTitle = titleLine.getContent();
+            } else if (attachment instanceof PSPageInfo) {
+                PSPageInfo pageInfo = (PSPageInfo)attachment;
+                currentPageInfo = pageInfo.getContent();
+            } else if (attachment instanceof PSSetupCode) {
+                if (setupCodeList == null) {
+                    setupCodeList = new java.util.ArrayList();
+                }
+                setupCodeList.add(attachment);
+            } else if (attachment instanceof PSSetPageDevice) {
+                /**
+                 * Extract all PSSetPageDevice instances from the
+                 * attachment list on the s-p-m and add all
+                 * dictionary entries to our internal representation
+                 * of the the page device dictionary.
+                 */
+                PSSetPageDevice setPageDevice = (PSSetPageDevice)attachment;
+                String content = setPageDevice.getContent();
+                if (content != null) {
+                    try {
+                        PSDictionary dictionary = (PSDictionary)PSDictionary.Maker
+                                .getInstance().make(content);
+                        pageDeviceDictionary.putAll(dictionary);
+                    } catch (PSObjectException e) {
+                        log.error(e.getMessage());
+                    }
+                }
+            }
+        }        
+    }
+    
     /** @see org.apache.fop.render.Renderer */
     public void processOffDocumentItem(OffDocumentItem oDI) {
         if (log.isDebugEnabled()) {
             log.debug("Handling OffDocumentItem: " + oDI.getName());
         }
+        super.processOffDocumentItem(oDI);
         if (oDI instanceof OffDocumentExtensionAttachment) {
             ExtensionAttachment attachment = ((OffDocumentExtensionAttachment)oDI).getAttachment();
-            if (PSSetupCode.CATEGORY.equals(attachment.getCategory())) {
-                PSSetupCode setupCode = (PSSetupCode)attachment;
-                if (setupCodeList == null) {
-                    setupCodeList = new java.util.ArrayList();
+            if (attachment != null) {
+                // handled separately
+                if (attachment instanceof PSPageInfo) {
+                    PSPageInfo pageInfo = (PSPageInfo)attachment;
+                    int whenToProcess = oDI.getWhenToProcess();
+                    if (whenToProcess == OffDocumentItem.BEFORE_PAGE_SEQ) {
+                        defaultPageInfo = pageInfo.getContent();
+                    } else if (whenToProcess == OffDocumentItem.IMMEDIATELY) {
+                        currentPageInfo = pageInfo.getContent();                        
+                    }
+                } else {
+                    processAttachment(attachment);
                 }
-                setupCodeList.add(setupCode);
             }
         }
-        super.processOffDocumentItem(oDI);
     }
     
-    /** @see org.apache.fop.render.Renderer#startPageSequence(org.apache.fop.area.LineArea) */
+/** @see org.apache.fop.render.Renderer#startPageSequence(org.apache.fop.area.LineArea) */
     public void startPageSequence(LineArea seqTitle) {
         super.startPageSequence(seqTitle);
-        if (!firstPageSequenceReceived) {
-            //Do this only once, as soon as we have all the content for the Setup section!
-            try {
-                //Prolog
-                gen.writeDSCComment(DSCConstants.BEGIN_PROLOG);
-                PSProcSets.writeStdProcSet(gen);
-                PSProcSets.writeEPSProcSet(gen);
-                gen.writeDSCComment(DSCConstants.END_PROLOG);
+        this.firstPageInSequence = true;
+    }
+    
+    private static final Integer ZERO = new Integer(0);
+    
+    /**
+     * Prepares postscript page setup for a page with a particular page master
+     * 
+     * @author Adrian Cumiskey
+     */
+    private class PSPageSetup {
+        private double pspagewidth;
+        private double pspageheight;
+        private String spmName;
+        private boolean rotate = false;
 
-                //Setup
-                gen.writeDSCComment(DSCConstants.BEGIN_SETUP);
-                writeSetupCodeList(setupCodeList, "SetupCode");
-                if (!twoPassGeneration) {
-                    this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo);
-                } else {
-                    gen.commentln("%FOPFontSetup");
+        /**
+         * default constructor
+         * @param page page viewport
+         */
+        private PSPageSetup(PageViewport page) {
+            long pagewidth = Math.round(page.getViewArea().getWidth());
+            long pageheight = Math.round(page.getViewArea().getHeight());
+            this.spmName = page.getSimplePageMasterName();
+            this.pspagewidth = pagewidth / 1000f;
+            this.pspageheight = pageheight / 1000f;
+            if (autoRotateLandscape && (pageheight < pagewidth)) {
+                rotate = true;
+                pageDeviceDictionary.put("/PageSize", new PSArrayObject() {
+                    {
+                        add("" + Math.round(pspageheight));
+                        add("" + Math.round(pspagewidth));
+                    }
+                });
+            } else {
+                pageDeviceDictionary.put("/PageSize", new PSArrayObject() {
+                    {
+                        add("" + Math.round(pspagewidth));
+                        add("" + Math.round(pspageheight));
+                    }
+                });
+            }
+            
+            if (page.hasExtensionAttachments()) {
+                Iterator iter = page.getExtensionAttachments().iterator();
+                while (iter.hasNext()) {
+                    ExtensionAttachment attachment = (ExtensionAttachment) iter.next();
+                    processAttachment(attachment);
                 }
-                gen.writeDSCComment(DSCConstants.END_SETUP);
-            } catch (IOException ioe) {
-                handleIOTrouble(ioe);
             }
-            
-            firstPageSequenceReceived = true;
         }
+
+        /**
+         * writes out the postscript page setup for the current page
+         * @throws IOException io exception
+         */
+        private void write(PageViewport page) throws IOException {            
+            try {
+                if (setupCodeList != null) {
+                    writeExtensionAttachments(setupCodeList);
+                    setupCodeList.clear();
+                }
+            } catch (IOException e) {
+                log.error(e.getMessage());
+            }
+            if (rotate) {
+                gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[] {
+                        ZERO, ZERO, new Long(Math.round(pspageheight)),
+                        new Long(Math.round(pspagewidth)) });
+                gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[] {
+                        ZERO, ZERO, new Double(pspageheight),
+                        new Double(pspagewidth) });
+                gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Landscape");
+            } else {
+                gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[] {
+                        ZERO, ZERO, new Long(Math.round(pspagewidth)),
+                        new Long(Math.round(pspageheight)) });
+                gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[] {
+                        ZERO, ZERO, new Double(pspagewidth),
+                        new Double(pspageheight) });
+                if (autoRotateLandscape) {
+                    gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION,
+                            "Portrait");
+                }
+            }
+            gen.writeDSCComment(DSCConstants.PAGE_RESOURCES,
+                    new Object[] { DSCConstants.ATEND });
+
+            gen.commentln("%FOPSimplePageMaster: " + spmName);
+
+            gen.writeDSCComment(DSCConstants.BEGIN_PAGE_SETUP);
+
+            // ps-page-info
+            if (currentPageInfo == null && defaultPageInfo != null) {
+                currentPageInfo = defaultPageInfo;
+            }
+            if (currentPageInfo != null) {
+                gen.commentln("%FOPPageInfo: " + currentPageInfo);
+                currentPageInfo = null;
+            }
+
+            // Write any unwritten changes to page device dictionary
+            if (pageDeviceDictionary.hasUnwrittenContent()) {
+                String content = pageDeviceDictionary.getUnWrittenContent()
+                        + " SPD";
+                writeExtensionAttachment(new PSSetPageDevice(content));
+            }
+
+            if (rotate) {
+                gen.writeln(Math.round(pspageheight) + " 0 translate");
+                gen.writeln("90 rotate");
+            }
+            concatMatrix(1, 0, 0, -1, 0, pspageheight);
+
+            gen.writeDSCComment(DSCConstants.END_PAGE_SETUP);            
+        }
     }
     
     /**
-     * Formats and writes a List of PSSetupCode instances to the output stream.
-     * @param setupCodeList a List of PSSetupCode instances
-     * @param type the type of code section
+     * Formats and writes a PSExtensionAttachment to the output stream.
+     * 
+     * @param attachment
+     *            an PSExtensionAttachment instance
      */
-    private void writeSetupCodeList(List setupCodeList, String type) throws IOException {
-        if (setupCodeList != null) {
-            Iterator i = setupCodeList.iterator();
-            while (i.hasNext()) {
-                PSSetupCode setupCode = (PSSetupCode)i.next();
-                gen.commentln("%FOPBegin" + type + ": (" 
-                        + (setupCode.getName() != null ? setupCode.getName() : "") 
-                        + ")");
-                LineNumberReader reader = new LineNumberReader(
-                        new java.io.StringReader(setupCode.getContent()));
-                String line;
-                while ((line = reader.readLine()) != null) {
-                    line = line.trim();
-                    if (line.length() > 0) {
-                        gen.writeln(line.trim());
-                    }
-                }
-                gen.commentln("%FOPEnd" + type);
-                i.remove();
+    private void writeExtensionAttachment(PSExtensionAttachment attachment)
+            throws IOException {
+        String info = "";
+        if (attachment instanceof PSSetupCode) {
+            PSSetupCode setupCode = (PSSetupCode)attachment;
+            String name = setupCode.getName();
+            if (name != null) {
+                info += ": (" + name + ")";
             }
         }
+        String type = attachment.getType();
+        gen.commentln("%FOPBegin" + type + info);
+        LineNumberReader reader = new LineNumberReader(
+                new java.io.StringReader(attachment.getContent()));
+        String line;
+        while ((line = reader.readLine()) != null) {
+            line = line.trim();
+            if (line.length() > 0) {
+                gen.writeln(line);
+            }
+        }
+        gen.commentln("%FOPEnd" + type);
     }
 
     /**
-     * @see org.apache.fop.render.Renderer#renderPage(PageViewport)
+     * Formats and writes a Collection of PSExtensionAttachment instances to
+     * the output stream.
+     * 
+     * @param attachmentCollection
+     *            a Collection of PSExtensionAttachment instances
      */
+    private void writeExtensionAttachments(Collection attachmentCollection)
+            throws IOException {
+        Iterator iter = attachmentCollection.iterator();
+        while (iter.hasNext()) {
+            PSExtensionAttachment attachment = (PSExtensionAttachment)iter
+                    .next();
+            if (attachment != null) {
+                writeExtensionAttachment(attachment);
+            }
+            iter.remove();
+        }
+    }
+
+    /**
+     * Writes the postscript header
+     * @param first page
+     * @throws IOException io exception
+     */
+    private void writeHeader(PageViewport page) throws IOException {
+        //PostScript Header
+        writeln(DSCConstants.PS_ADOBE_30);
+        gen.writeDSCComment(DSCConstants.CREATOR, new String[] {userAgent.getProducer()});
+        gen.writeDSCComment(DSCConstants.CREATION_DATE, new Object[] {new java.util.Date()});
+        gen.writeDSCComment(DSCConstants.LANGUAGE_LEVEL, new Integer(gen.getPSLevel()));
+        gen.writeDSCComment(DSCConstants.PAGES, new Object[] {DSCConstants.ATEND});
+        gen.writeDSCComment(DSCConstants.DOCUMENT_SUPPLIED_RESOURCES, 
+                new Object[] {DSCConstants.ATEND});
+
+        if (dscTitle != null) {
+            gen.writeDSCComment(DSCConstants.TITLE, dscTitle);            
+        }
+        
+        gen.writeDSCComment(DSCConstants.END_COMMENTS);
+
+        //Defaults
+        gen.writeDSCComment(DSCConstants.BEGIN_DEFAULTS);
+        gen.writeDSCComment(DSCConstants.END_DEFAULTS);
+
+        //Do this only once, as soon as we have all the content for the Setup section!
+        try {
+            //Prolog
+            gen.writeDSCComment(DSCConstants.BEGIN_PROLOG);
+            PSProcSets.writeStdProcSet(gen);
+            PSProcSets.writeEPSProcSet(gen);
+            FOPProcSet.writeFOPProcSet(gen);
+            gen.writeDSCComment(DSCConstants.END_PROLOG);
+
+            //Setup
+            gen.writeDSCComment(DSCConstants.BEGIN_SETUP);
+            if (!twoPassGeneration) {
+                this.fontResources = PSFontUtils.writeFontDict(gen, fontInfo);
+            } else {
+                gen.commentln("%FOPFontSetup");
+            }
+            gen.writeDSCComment(DSCConstants.END_SETUP);
+        } catch (IOException ioe) {
+            handleIOTrouble(ioe);
+        }
+    }
+    
+    /** @see org.apache.fop.render.Renderer#renderPage(PageViewport) */
     public void renderPage(PageViewport page)
             throws IOException, FOPException {
-        log.debug("renderPage(): " + page);
+        if (log.isDebugEnabled()) {
+            log.debug(page);
+        }
 
+        if (currentPageNumber == 0) {
+            writeHeader(page);
+        }
+        
         this.currentPageNumber++;
+        
         gen.getResourceTracker().notifyStartNewPage();
         gen.getResourceTracker().notifyResourceUsageOnPage(PSProcSets.STD_PROCSET);
         gen.writeDSCComment(DSCConstants.PAGE, new Object[]
                 {page.getPageNumberString(),
                  new Integer(this.currentPageNumber)});
-        final Integer zero = new Integer(0);
-        final long pagewidth = Math.round(page.getViewArea().getWidth());
-        final long pageheight = Math.round(page.getViewArea().getHeight());
-        final double pspagewidth = pagewidth / 1000f;
-        final double pspageheight = pageheight / 1000f;
-        boolean rotate = false;
-        if (this.autoRotateLandscape && (pageheight < pagewidth)) {
-            rotate = true;
-            gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[]
-                    {zero,
-                     zero,
-                     new Long(Math.round(pspageheight)),
-                     new Long(Math.round(pspagewidth))});
-            gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[]
-                    {zero,
-                     zero,
-                     new Double(pspageheight),
-                     new Double(pspagewidth)});
-            gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Landscape");
-        } else {
-            gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[]
-                    {zero,
-                     zero,
-                     new Long(Math.round(pspagewidth)),
-                     new Long(Math.round(pspageheight))});
-            gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[]
-                    {zero,
-                     zero,
-                     new Double(pspagewidth),
-                     new Double(pspageheight)});
-            if (this.autoRotateLandscape) {
-                gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION, "Portrait");
+
+        // if this is the first page in a sequence, potentially use a different page master
+        if (firstPageInSequence) {
+            String spmName = page.getSimplePageMasterName();
+            // already created the page setup for this master?
+            currentPageSetup = (PSPageSetup)pageSetupMap.get(spmName);
+            if (currentPageSetup == null) {
+                // create new page setup for new page master
+                currentPageSetup = new PSPageSetup(page);                
+                pageSetupMap.put(spmName, currentPageSetup);
             }
+            firstPageInSequence = false;
         }
-        gen.writeDSCComment(DSCConstants.PAGE_RESOURCES, 
-                new Object[] {DSCConstants.ATEND});
-        gen.commentln("%FOPSimplePageMaster: " + page.getSimplePageMasterName());
-        gen.writeDSCComment(DSCConstants.BEGIN_PAGE_SETUP);
-        
-        //Handle PSSetupCode instances on simple-page-master
-        if (page.getExtensionAttachments() != null 
-                && page.getExtensionAttachments().size() > 0) {
-            List list = new java.util.ArrayList();
-            //Extract all PSSetupCode instances from the attachment list on the s-p-m
-            Iterator i = page.getExtensionAttachments().iterator();
-            while (i.hasNext()) {
-                ExtensionAttachment attachment = (ExtensionAttachment)i.next();
-                if (PSSetupCode.CATEGORY.equals(attachment.getCategory())) {
-                    list.add(attachment);
-                }
-            }
-            writeSetupCodeList(list, "PageSetupCode");
-        }
-        
-        if (rotate) {
-            gen.writeln("<<");
-            gen.writeln("/PageSize [" 
-                    + Math.round(pspageheight) + " " 
-                    + Math.round(pspagewidth) + "]");
-            gen.writeln("/ImagingBBox null");
-            gen.writeln(">> setpagedevice");
-            gen.writeln(Math.round(pspageheight) + " 0 translate");
-            gen.writeln("90 rotate");
-        } else {
-            gen.writeln("<<");
-            gen.writeln("/PageSize [" 
-                    + Math.round(pspagewidth) + " " 
-                    + Math.round(pspageheight) + "]");
-            gen.writeln("/ImagingBBox null");
-            gen.writeln(">> setpagedevice");
-        }
-        concatMatrix(1, 0, 0, -1, 0, pageheight / 1000f);
+        // write page setup
+        currentPageSetup.write(page);
 
-        gen.writeDSCComment(DSCConstants.END_PAGE_SETUP);
-
         //Process page
         super.renderPage(page);
 
+        //Show page
         writeln("showpage");
         gen.writeDSCComment(DSCConstants.PAGE_TRAILER);
         gen.getResourceTracker().writeResources(true, gen);
@@ -1068,6 +1287,12 @@
         super.renderSpace(space);
     }
 
+    /**
+     * Renders text
+     * @param area text area
+     * @param text text string to render
+     * @param letterAdjust
+     */
     private void renderText(AbstractTextArea area, String text, int[] letterAdjust) {
         Font font = getFontFromArea(area);
         Typeface tf = (Typeface) fontInfo.getFonts().get(font.getFontName());
@@ -1296,5 +1521,4 @@
         return MIME_TYPE;
     }
 
-
 }
Index: src/java/org/apache/fop/render/ps/extensions/PSExtensionElementMapping.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSExtensionElementMapping.java	(revision 529569)
+++ src/java/org/apache/fop/render/ps/extensions/PSExtensionElementMapping.java	(working copy)
@@ -41,6 +41,9 @@
             foObjs = new java.util.HashMap();
             foObjs.put("ps-setup-code", new PSSetupCodeMaker());
             foObjs.put("ps-page-setup-code", new PSPageSetupCodeMaker());
+            foObjs.put("ps-setpagedevice", new PSSetPageDeviceMaker());
+            foObjs.put("ps-page-info", new PSPageInfoMaker());
+            foObjs.put("ps-title-line", new PSTitleLineMaker());
         }
     }
 
@@ -56,4 +59,21 @@
         }
     }
 
+    static class PSSetPageDeviceMaker extends ElementMapping.Maker {
+        public FONode make(FONode parent) {
+            return new PSSetPageDeviceElement(parent);
+        }
+    }
+
+    static class PSPageInfoMaker extends ElementMapping.Maker {
+        public FONode make(FONode parent) {
+            return new PSPageInfoElement(parent);
+        }
+    }
+
+    static class PSTitleLineMaker extends ElementMapping.Maker {
+        public FONode make(FONode parent) {
+            return new PSTitleLineElement(parent);
+        }
+    }
 }
Index: src/java/org/apache/fop/render/ps/extensions/PSPageSetupCodeElement.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSPageSetupCodeElement.java	(revision 529569)
+++ src/java/org/apache/fop/render/ps/extensions/PSPageSetupCodeElement.java	(working copy)
@@ -22,7 +22,11 @@
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
 
 /**
  * Extension element for fox:ps-page-setup-code. 
@@ -50,4 +54,18 @@
         return "ps-page-setup-code";
     }
 
+    /** @see org.apache.fop.render.ps.extensions.AbstractPSExtensionObject#instantiateExtensionAttachment() */
+    public ExtensionAttachment instantiateExtensionAttachment() {
+        return new PSSetupCode();
+    }
+    
+    /** @see org.apache.fop.fo.FONode#processNode */
+    public void processNode(String elementName, Locator locator, 
+                            Attributes attlist, PropertyList propertyList)
+                                throws FOPException {
+        String name = attlist.getValue("name");
+        if (name != null && name.length() > 0) {
+            ((PSSetupCode)attachment).setName(name);
+        }
+    }
 }
Index: src/java/org/apache/fop/render/ps/extensions/PSPageInfoElement.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSPageInfoElement.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/extensions/PSPageInfoElement.java	(revision 0)
@@ -0,0 +1,52 @@
+/**
+ * 
+ */
+package org.apache.fop.render.ps.extensions;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+
+/**
+ * 
+ * Page info element
+ * 
+ * @author Adrian Cumiskey
+ *
+ */
+public class PSPageInfoElement extends AbstractPSExtensionObject {
+
+    /**
+     * @see org.apache.fop.fo.FONode#FONode(FONode)
+     */
+    public PSPageInfoElement(FONode parent) {
+        super(parent);
+    }
+
+    /**
+     * @see org.apache.fop.fo.FONode#getLocalName()
+     * @return local name 
+     */
+    public String getLocalName() {
+        return "ps-page-info";
+    }
+
+    /** @see org.apache.fop.fo.FONode#startOfNode() */
+    protected void startOfNode() throws FOPException {
+        super.startOfNode();
+        if ( !((parent.getNameId() == Constants.FO_DECLARATIONS)
+                || (parent.getNameId() == Constants.FO_SIMPLE_PAGE_MASTER)) ) {
+            throw new ValidationException( getName()
+                    + " must be a child of fo:declarations or fo:simple-page-master.");
+        }
+    }
+
+    /**
+     * @see org.apache.fop.render.ps.extensions.AbstractPSExtensionObject#instantiateExtensionAttachment()
+     */
+    protected ExtensionAttachment instantiateExtensionAttachment() {
+        return new PSPageInfo();
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/extensions/PSTitleLineElement.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSTitleLineElement.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/extensions/PSTitleLineElement.java	(revision 0)
@@ -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.render.ps.extensions;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+
+/**
+ * 
+ * Title line element
+ * 
+ * @author Adrian Cumiskey
+ * 
+ */
+public class PSTitleLineElement extends AbstractPSExtensionObject {
+
+    /**
+     * default constructor
+     * 
+     * @param parent
+     */
+    public PSTitleLineElement(FONode parent) {
+        super(parent);
+    }
+
+    /** @see org.apache.fop.fo.FONode#getLocalName() */
+    public String getLocalName() {
+        return PSTitleLine.ELEMENT;
+    }
+
+    /** @see org.apache.fop.fo.FONode#startOfNode() */
+    protected void startOfNode() throws FOPException {
+        super.startOfNode();
+        if (parent.getNameId() != Constants.FO_DECLARATIONS) {
+            throw new ValidationException(getName() + " must be a child of fo:declarations.");
+        }
+    }
+
+    /** @see org.apache.fop.render.ps.extensions.AbstractPSExtensionObject#instantiateExtensionAttachment() */
+    protected ExtensionAttachment instantiateExtensionAttachment() {
+        return new PSTitleLine();
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/extensions/PSSetupCodeElement.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSSetupCodeElement.java	(revision 529569)
+++ src/java/org/apache/fop/render/ps/extensions/PSSetupCodeElement.java	(working copy)
@@ -22,7 +22,11 @@
 import org.apache.fop.apps.FOPException;
 import org.apache.fop.fo.Constants;
 import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
 
 /**
  * Extension element for fox:ps-setup-code. 
@@ -45,9 +49,23 @@
         }
     }
     
+    /** @see org.apache.fop.fo.FONode#processNode */
+    public void processNode(String elementName, Locator locator, 
+                            Attributes attlist, PropertyList propertyList)
+                                throws FOPException {
+        String name = attlist.getValue("name");
+        if (name != null && name.length() > 0) {
+            ((PSSetupCode)attachment).setName(name);
+        }
+    }
+
     /** @see org.apache.fop.fo.FONode#getLocalName() */
     public String getLocalName() {
         return "ps-setup-code";
     }
-    
+
+    /** @see org.apache.fop.render.ps.extensions.AbstractPSExtensionObject#instantiateExtensionAttachment() */
+    protected ExtensionAttachment instantiateExtensionAttachment() {
+        return new PSSetupCode();
+    }
 }
Index: src/java/org/apache/fop/render/ps/extensions/PSSetPageDevice.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSSetPageDevice.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/extensions/PSSetPageDevice.java	(revision 0)
@@ -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.render.ps.extensions;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Element for postscript setpagedevice instruction
+ * This is a an extension which provides a pass-through value
+ * dictionary object for the postscript setpagedevice instruction.
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSSetPageDevice extends PSExtensionAttachment {
+    /** element name */
+    protected static final String ELEMENT = "ps-setpagedevice";
+
+    private static final String ATT_NAME = "name";
+
+    /**
+     * name attribute
+     */
+    protected String name = null;
+
+    /**
+     * default constructor
+     * @param content set page device dictionary
+     */
+    public PSSetPageDevice(String content) {
+        super(content);
+    }
+
+    /**
+     * constructor
+     * @param name name attribute of this setpagedevice content
+     * @param content set page device dictionary
+     */
+    public PSSetPageDevice(String name, String content) {
+        this(content);
+        this.name = name;
+    }
+
+    /**
+     * constructor
+     */
+    public PSSetPageDevice() {
+    }
+    
+    /** @return the name */
+    public String getName() {
+        return name;
+    }
+    
+    /**
+     * Sets the name of the setup code object.
+     * @param name The name to set.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /** @see java.lang.Object#toString() */
+    public String toString() {
+        return "PSSetPageDevice(name=" + getName() + ", content='" + getContent() + "')";
+    }
+
+    /* @see org.apache.fop.render.ps.extensions.PSExtensionAttachment#getElement() */
+    protected String getElement() {
+        return ELEMENT;
+    }
+    
+    /**
+     * @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler)
+     */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        if (name != null && name.length() > 0) {
+            atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", name);
+        }
+        String element = getElement();
+        handler.startElement(CATEGORY, element, element, atts);
+        if (content != null && content.length() > 0) {
+            char[] chars = content.toCharArray();
+            handler.characters(chars, 0, chars.length);
+        }
+        handler.endElement(CATEGORY, element, element);
+    }
+}
Index: src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java	(revision 529569)
+++ src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java	(working copy)
@@ -19,6 +19,8 @@
 
 package org.apache.fop.render.ps.extensions;
 
+import java.util.List;
+
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.fop.util.ContentHandlerFactory;
@@ -39,24 +41,30 @@
     private StringBuffer content = new StringBuffer();
     private Attributes lastAttributes;
     
-    private PSSetupCode returnedObject;
+    private PSExtensionAttachment returnedObject;
     private ObjectBuiltListener listener;
     
+    private static final List PS_ELEMENTS = new java.util.ArrayList() { {
+        add(PSSetupCode.ELEMENT);
+        add(PSSetPageDevice.ELEMENT);
+        add(PSTitleLine.ELEMENT);
+        add(PSPageInfo.ELEMENT);
+    } };
+    
     /** @see org.xml.sax.helpers.DefaultHandler */
     public void startElement(String uri, String localName, String qName, Attributes attributes) 
                 throws SAXException {
         boolean handled = false;
-        if (PSSetupCode.CATEGORY.equals(uri)) {
+        if (PSExtensionAttachment.CATEGORY.equals(uri)) {
             lastAttributes = attributes;
-            handled = true; 
-            if ("ps-setup-code".equals(localName)) {
+            handled = false; 
+            if (PS_ELEMENTS.contains(localName)) {
                 //handled in endElement
-            } else {
-                handled = false;
+                handled = true;
             }
         }
         if (!handled) {
-            if (PSSetupCode.CATEGORY.equals(uri)) {
+            if (PSExtensionAttachment.CATEGORY.equals(uri)) {
                 throw new SAXException("Unhandled element " + localName 
                         + " in namespace: " + uri);
             } else {
@@ -68,10 +76,17 @@
 
     /** @see org.xml.sax.helpers.DefaultHandler */
     public void endElement(String uri, String localName, String qName) throws SAXException {
-        if (PSSetupCode.CATEGORY.equals(uri)) {
-            if ("ps-setup-code".equals(localName)) {
+        if (PSExtensionAttachment.CATEGORY.equals(uri)) {
+            if (PSSetupCode.ELEMENT.equals(localName)) {
                 String name = lastAttributes.getValue("name");
                 this.returnedObject = new PSSetupCode(name, content.toString());
+            } else if(PSSetPageDevice.ELEMENT.equals(localName)) {
+                String name = lastAttributes.getValue("name");
+                this.returnedObject = new PSSetPageDevice(name, content.toString());                
+            } else if(PSTitleLine.ELEMENT.equals(localName)) {
+                this.returnedObject = new PSTitleLine(content.toString());                
+            } else if(PSPageInfo.ELEMENT.equals(localName)) {
+                this.returnedObject = new PSPageInfo(content.toString());                
             }
         }    
         content.setLength(0); //Reset text buffer (see characters())
Index: src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java	(revision 0)
@@ -0,0 +1,77 @@
+/*
+ * 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.render.ps.extensions;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.PropertyList;
+import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
+/**
+ * 
+ * Extension element for ps:ps-setpagedevice. 
+ * 
+ * @author Adrian Cumiskey
+ * 
+ */
+public class PSSetPageDeviceElement extends AbstractPSExtensionObject {
+
+    /**
+     * Main constructor
+     * @param parent parent FO node
+     */
+    protected PSSetPageDeviceElement(FONode parent) {
+        super(parent);
+    }
+
+    /** @see org.apache.fop.fo.FONode#startOfNode() */
+    protected void startOfNode() throws FOPException {
+        super.startOfNode();
+        if ( !((parent.getNameId() == Constants.FO_DECLARATIONS)
+                || (parent.getNameId() == Constants.FO_SIMPLE_PAGE_MASTER)) ) {
+            throw new ValidationException( getName()
+                    + " must be a child of fo:declarations or fo:simple-page-master.");
+        }
+    }
+
+    /** @see org.apache.fop.fo.FONode#processNode */
+    public void processNode(String elementName, Locator locator, 
+                            Attributes attlist, PropertyList propertyList)
+                                throws FOPException {
+        String name = attlist.getValue("name");
+        if (name != null && name.length() > 0) {
+            ((PSSetPageDevice)attachment).setName(name);
+        }
+    }
+
+    /** @see org.apache.fop.fo.FONode#getLocalName() */
+    public String getLocalName() {
+        return "ps-setpagedevice";
+    }
+
+    /** @see org.apache.fop.render.ps.extensions.AbstractPSExtensionObject#instantiateExtensionAttachment() */
+    protected ExtensionAttachment instantiateExtensionAttachment() {
+        return new PSSetPageDevice();
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/extensions/PSExtensionAttachment.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSExtensionAttachment.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/extensions/PSExtensionAttachment.java	(revision 0)
@@ -0,0 +1,91 @@
+/*
+ * 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: PSSetupCode.java 426576 2006-07-28 15:44:37Z jeremias $ */
+package org.apache.fop.render.ps.extensions;
+
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.util.XMLizable;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * This is the pass-through value object for the PostScript extension.
+ * 
+ * @author Adrian Cumiskey
+ */
+public abstract class PSExtensionAttachment implements ExtensionAttachment, XMLizable {
+    /** extension node content */
+    protected String content;
+
+    /** The category URI for this extension attachment. */
+    public static final String CATEGORY = "apache:fop:extensions:postscript";
+
+    /**
+     * Default constructor.
+     * @param content the content of the setup code object
+     */
+    public PSExtensionAttachment(String content) {
+        this.content = content;
+    }
+
+    /**
+     * No-argument contructor.
+     */
+    public PSExtensionAttachment() {
+    }
+
+    /** @see org.apache.fop.fo.extensions.ExtensionAttachment#getCategory() */
+    public String getCategory() {
+        return CATEGORY;
+    }
+    
+    /** @return the content */
+    public String getContent() {
+        return content;
+    }
+    
+    /**
+     * Sets the content for the setup code object.
+     * @param content The content to set.
+     */
+    public void setContent(String content) {
+        this.content = content;
+    }
+           
+    /** @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler) */
+    public void toSAX(ContentHandler handler) throws SAXException {
+        AttributesImpl atts = new AttributesImpl();
+        String element = getElement();
+        handler.startElement(CATEGORY, element, element, atts);
+        if (content != null && content.length() > 0) {
+            char[] chars = content.toCharArray();
+            handler.characters(chars, 0, chars.length);
+        }
+        handler.endElement(CATEGORY, element, element);
+    }
+
+    /** @return type name */
+    public String getType() {
+        String className = getClass().getName();
+        return className.substring(className.lastIndexOf('.')+3);
+    }
+
+    /** @return element */
+    protected abstract String getElement();
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionObject.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionObject.java	(revision 529569)
+++ src/java/org/apache/fop/render/ps/extensions/AbstractPSExtensionObject.java	(working copy)
@@ -25,7 +25,6 @@
 import org.apache.fop.fo.PropertyList;
 import org.apache.fop.fo.ValidationException;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
-import org.xml.sax.Attributes;
 import org.xml.sax.Locator;
 
 /**
@@ -33,13 +32,14 @@
  */
 public abstract class AbstractPSExtensionObject extends FONode {
 
-    private PSSetupCode setupCode = new PSSetupCode();
+    protected PSExtensionAttachment attachment;
     
     /**
      * @see org.apache.fop.fo.FONode#FONode(FONode)
      */
     public AbstractPSExtensionObject(FONode parent) {
         super(parent);
+        this.attachment = (PSExtensionAttachment)instantiateExtensionAttachment();
     }
 
     /**
@@ -56,12 +56,12 @@
     /** @see org.apache.fop.fo.FONode */
     protected void addCharacters(char[] data, int start, int length,
                                  PropertyList pList, Locator locator) {
-        if (setupCode.getContent() != null) {
-            StringBuffer sb = new StringBuffer(setupCode.getContent());
+        if (attachment.getContent() != null) {
+            StringBuffer sb = new StringBuffer(attachment.getContent());
             sb.append(data, start, length - start);
-            setupCode.setContent(sb.toString());
+            attachment.setContent(sb.toString());
         } else {
-            setupCode.setContent(new String(data, start, length - start));
+            attachment.setContent(new String(data, start, length - start));
         }
     }
 
@@ -75,20 +75,10 @@
         return "fox";
     }
 
-    /** @see org.apache.fop.fo.FONode#processNode */
-    public void processNode(String elementName, Locator locator, 
-                            Attributes attlist, PropertyList propertyList)
-                                throws FOPException {
-        String name = attlist.getValue("name");
-        if (name != null && name.length() > 0) {
-            setupCode.setName(name);
-        }
-    }
-
     /** @see org.apache.fop.fo.FONode#endOfNode() */
     protected void endOfNode() throws FOPException {
         super.endOfNode();
-        String s = setupCode.getContent(); 
+        String s = attachment.getContent();
         if (s == null || s.length() == 0) {
             missingChildElementError("#PCDATA");
         }
@@ -96,8 +86,13 @@
     
     /** @see org.apache.fop.fo.FONode#getExtensionAttachment() */
     public ExtensionAttachment getExtensionAttachment() {
-        return this.setupCode;
+        return this.attachment;
     }
-
+    
+    /**
+     * Instantiates extension attachment object
+     * @return extension attachment
+     */
+    protected abstract ExtensionAttachment instantiateExtensionAttachment();
 }
 
Index: src/java/org/apache/fop/render/ps/extensions/PSPageInfo.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSPageInfo.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/extensions/PSPageInfo.java	(revision 0)
@@ -0,0 +1,53 @@
+/*
+ * 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.render.ps.extensions;
+
+/**
+ * postcript page info extension
+ * 
+ * @author Adrian Cumiskey
+ */
+public class PSPageInfo extends PSExtensionAttachment {
+
+    /** element name */
+    protected static final String ELEMENT = "ps-page-info";
+
+    /**
+     * default constructor
+     * @param pageInfoStr
+     */
+    public PSPageInfo(String pageInfoStr) {
+        super(pageInfoStr);
+    }
+
+    public PSPageInfo() {
+        super();
+    }
+
+    /* @see org.apache.fop.render.ps.extensions.PSExtensionAttachment#getElement() */
+    protected String getElement() {
+        return ELEMENT;
+    }
+    
+    /** @see java.lang.Object#toString() */
+    public String toString() {
+        return "PSPageInfo(content='" + getContent() + "')";
+    }
+}
Index: src/java/org/apache/fop/render/ps/extensions/PSTitleLine.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSTitleLine.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/extensions/PSTitleLine.java	(revision 0)
@@ -0,0 +1,61 @@
+/*
+ * 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.render.ps.extensions;
+
+/**
+ * 
+ * Title line implementation
+ * 
+ * @author Adrian Cumiskey
+ *
+ */
+public class PSTitleLine extends PSExtensionAttachment {
+
+    /**
+     * Element name
+     */
+    protected static final String ELEMENT = "ps-title-line";
+
+    /**
+     * No-argument contructor.
+     */
+    public PSTitleLine() {
+    }
+
+    /**
+     * default constructor
+     * @param title title
+     */
+    public PSTitleLine(String title) {
+        super(title);
+    }
+
+    /**
+     * @see org.apache.fop.render.ps.extensions.PSExtensionAttachment#getElement() 
+     * @return element 
+     */
+    protected String getElement() {
+        return ELEMENT;
+    }
+    
+    /** @see java.lang.Object#toString() */
+    public String toString() {
+        return "PSTitleLine(content='" + getContent() + "')";
+    }
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java
===================================================================
--- src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java	(revision 529569)
+++ src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java	(working copy)
@@ -19,10 +19,6 @@
 
 package org.apache.fop.render.ps.extensions;
 
-import java.io.Serializable;
-
-import org.apache.fop.fo.extensions.ExtensionAttachment;
-import org.apache.fop.util.XMLizable;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.SAXException;
 import org.xml.sax.helpers.AttributesImpl;
@@ -30,19 +26,23 @@
 /**
  * This is the pass-through value object for the PostScript extension.
  */
-public class PSSetupCode implements ExtensionAttachment, Serializable, XMLizable {
-
-    /** The category URI for this extension attachment. */
-    public static final String CATEGORY = "apache:fop:extensions:postscript";
+public class PSSetupCode extends PSExtensionAttachment {
+    /**
+     * element name
+     */
+    protected static final String ELEMENT = "ps-setup-code";
     
-    private String name;
-    private String content;
+    private static final String ATT_NAME = "name";
 
     /**
+     * name attribute
+     */
+    protected String name = null;
+
+    /**
      * No-argument contructor.
      */
     public PSSetupCode() {
-        //nop
     }
     
     /**
@@ -51,23 +51,10 @@
      * @param content the content of the setup code object
      */
     public PSSetupCode(String name, String content) {
+        super(content);
         this.name = name;
-        this.content = content;
     }
-    
-    /** @return the content */
-    public String getContent() {
-        return content;
-    }
-    
-    /**
-     * Sets the content for the setup code object.
-     * @param content The content to set.
-     */
-    public void setContent(String content) {
-        this.content = content;
-    }
-    
+            
     /** @return the name */
     public String getName() {
         return name;
@@ -81,31 +68,32 @@
         this.name = name;
     }
 
-    /** @see org.apache.fop.fo.extensions.ExtensionAttachment#getCategory() */
-    public String getCategory() {
-        return CATEGORY;
-    }
-    
     /** @see java.lang.Object#toString() */
     public String toString() {
-        return "PSSetupCode(name=" + getName() + ")";
+        return "PSSetupCode(name=" + getName() + ", content='" + getContent() + "')";
     }
 
-    private static final String ATT_NAME = "name";
-    private static final String ELEMENT = "ps-setup-code";
+    /**
+     * @see org.apache.fop.render.ps.extensions.PSExtensionAttachment#getElement()
+     */
+    protected String getElement() {
+        return ELEMENT;
+    }
     
-    /** @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler) */
+    /**
+     * @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler)
+     */
     public void toSAX(ContentHandler handler) throws SAXException {
         AttributesImpl atts = new AttributesImpl();
         if (name != null && name.length() > 0) {
             atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", name);
         }
-        handler.startElement(CATEGORY, ELEMENT, ELEMENT, atts);
+        String element = getElement();
+        handler.startElement(CATEGORY, element, element, atts);
         if (content != null && content.length() > 0) {
             char[] chars = content.toCharArray();
             handler.characters(chars, 0, chars.length);
         }
-        handler.endElement(CATEGORY, ELEMENT, ELEMENT);
+        handler.endElement(CATEGORY, element, element);
     }
-    
-}
+}
\ No newline at end of file
Index: src/java/org/apache/fop/render/ps/FOPProcSet.java
===================================================================
--- src/java/org/apache/fop/render/ps/FOPProcSet.java	(revision 0)
+++ src/java/org/apache/fop/render/ps/FOPProcSet.java	(revision 0)
@@ -0,0 +1,120 @@
+/*
+ * 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: PSRenderer.java 520797 2007-03-21 08:13:21Z jeremias $ */
+
+package org.apache.fop.render.ps;
+
+import java.io.IOException;
+
+import org.apache.xmlgraphics.ps.DSCConstants;
+import org.apache.xmlgraphics.ps.PSGenerator;
+import org.apache.xmlgraphics.ps.PSProcSet;
+import org.apache.xmlgraphics.ps.PSResource;
+
+// TODO: consider moving this to xmlgraphics-commons
+
+/**
+ * FOP specific postscript procedures
+ * 
+ * @author Adrian Cumiskey
+ */
+public class FOPProcSet extends PSProcSet {
+    /** the standard procset for FOP */
+    public static final PSResource FOP_PROCSET = new FOPProcSet();
+
+    /**
+     * Default constructor
+     */
+    public FOPProcSet() {
+        super("Apache XML Graphics FOP ProcSet", 1.0f, 0);
+    }
+    
+    /**
+     * Generates a resource defining standard procset with operations used by the
+     * XML Graphics FOP project.
+     * @param gen PSGenerator to use for output
+     * @throws IOException In case of an I/O problem
+     */
+    public static void writeFOPProcSet(PSGenerator gen) throws IOException {
+        ((FOPProcSet)FOP_PROCSET).writeTo(gen);
+    }
+
+    /**
+     * Writes the procedure set to the postscript generator
+     * @param gen ps generator
+     * @throws IOException io exception
+     */
+    private void writeTo(PSGenerator gen) throws IOException {
+        gen.writeDSCComment(DSCConstants.BEGIN_RESOURCE, 
+                new Object[] {TYPE_PROCSET, getName(), 
+                    Float.toString(getVersion()), Integer.toString(getRevision())});
+        gen.writeDSCComment(DSCConstants.VERSION, 
+                new Object[] {Float.toString(getVersion()), Integer.toString(getRevision())});
+        gen.writeDSCComment(DSCConstants.COPYRIGHT, "Copyright 2002-2003 "
+                + "The Apache Software Foundation. "
+                + "License terms: http://www.apache.org/licenses/LICENSE-2.0");
+        gen.writeDSCComment(DSCConstants.TITLE, 
+                "FOP procedures used by the FOP Apache XML Graphics project");
+
+        gen.writeln("/DE {");
+        gen.writeln("  /o2 exch def");
+        gen.writeln("  /o1 exch def");
+        gen.writeln("  o1 o2 eq {true} {");
+        gen.writeln("    o1 type o2 type ne {false} {");
+        gen.writeln("      o1 type /dicttype eq {");
+        gen.writeln("        /len o1 length def");
+        gen.writeln("        len o2 length ne {false} {");
+        gen.writeln("          true");
+        gen.writeln("          o1 {");
+        gen.writeln("            exch o2 exch get ne {pop false exit} if");
+        gen.writeln("          } forall");
+        gen.writeln("        } ifelse");            
+        gen.writeln("      } {");
+        gen.writeln("        o1 type /arraytype eq {");
+        gen.writeln("          /len o1 length def");
+        gen.writeln("          len o2 length ne {false} {");
+        gen.writeln("            true");
+        gen.writeln("            0 1 len 1 sub {");
+        gen.writeln("              /idxarr exch def");
+        gen.writeln("              o1 idxarr get o2 idxarr get ne {pop false exit} if");
+        gen.writeln("            } for");
+        gen.writeln("          } ifelse");          
+        gen.writeln("        } {false} ifelse");
+        gen.writeln("      } ifelse");
+        gen.writeln("    } ifelse");
+        gen.writeln("  } ifelse");
+        gen.writeln("} bind def");
+        
+        gen.writeln("/SPD {");
+        gen.writeln("  dup length /d exch dict def");
+        gen.writeln("  {");
+        gen.writeln("    /v exch def");
+        gen.writeln("    /k exch def");
+        gen.writeln("    currentpagedevice k known {");
+        gen.writeln("      currentpagedevice k get v DE not {");
+        gen.writeln("        safedict k v put");
+        gen.writeln("      } if");
+        gen.writeln("    } if");
+        gen.writeln("  } forall");
+        gen.writeln("  d length 0 ne {d setpagedevice} if");
+        gen.writeln("} bind def");
+
+        gen.writeDSCComment(DSCConstants.END_RESOURCE);
+        gen.getResourceTracker().registerSuppliedResource(this);
+    }
+}
Index: src/java/org/apache/fop/render/Renderer.java
===================================================================
--- src/java/org/apache/fop/render/Renderer.java	(revision 529569)
+++ src/java/org/apache/fop/render/Renderer.java	(working copy)
@@ -25,6 +25,7 @@
 
 // FOP
 import org.apache.fop.apps.FOPException;
+import org.apache.fop.area.PageSequence;
 import org.apache.fop.area.PageViewport;
 import org.apache.fop.area.LineArea;
 import org.apache.fop.area.OffDocumentItem;
@@ -141,9 +142,9 @@
     /**
      * Tells the renderer that a new page sequence starts.
      *
-     * @param seqTitle  The title of the page sequence
+     * @param currentPageSequence  The title of the page sequence
      */
-    void startPageSequence(LineArea seqTitle);
+    void startPageSequence(LineArea title);
 
     /**
      * Tells the renderer to render a particular page. A renderer typically
Index: src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java
===================================================================
--- src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java	(revision 529569)
+++ src/java/org/apache/fop/layoutmgr/PageSequenceLayoutManager.java	(working copy)
@@ -141,12 +141,12 @@
                 // empty title; do nothing
             }
         }
-
         areaTreeHandler.getAreaTreeModel().startPageSequence(title);
-        log.debug("Starting layout");
+        if (log.isDebugEnabled()) {
+            log.debug("Starting layout");
+        }
 
         curPage = makeNewPage(false, false);
-
         
         Flow mainFlow = pageSeq.getMainFlow();
         childFLM = getLayoutManagerMaker().
Index: src/java/org/apache/fop/fo/pagination/SimplePageMaster.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/SimplePageMaster.java	(revision 529569)
+++ src/java/org/apache/fop/fo/pagination/SimplePageMaster.java	(working copy)
@@ -20,7 +20,6 @@
 package org.apache.fop.fo.pagination;
 
 // Java
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
@@ -98,7 +97,7 @@
         }
 
         //Well, there are only 5 regions so we can save a bit of memory here
-        regions = new HashMap(5);
+        regions = new java.util.HashMap(5);
     }
 
     /**
Index: src/java/org/apache/fop/fo/pagination/LayoutMasterSet.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/LayoutMasterSet.java	(revision 529569)
+++ src/java/org/apache/fop/fo/pagination/LayoutMasterSet.java	(working copy)
@@ -149,6 +149,11 @@
         this.simplePageMasters.put(masterName, sPM);
     }
 
+    /**
+     * TODO:
+     * @param masterName
+     * @return
+     */
     private boolean existsName(String masterName) {
         if (simplePageMasters.containsKey(masterName)
                 || pageSequenceMasters.containsKey(masterName)) {
Index: src/java/org/apache/fop/fo/pagination/Root.java
===================================================================
--- src/java/org/apache/fop/fo/pagination/Root.java	(revision 529569)
+++ src/java/org/apache/fop/fo/pagination/Root.java	(working copy)
@@ -231,6 +231,14 @@
     }
 
     /**
+     * Returns whether root has a declarations instance
+     * @return boolean true/false
+     */
+    public boolean hasDeclarations() {
+        return this.declarations != null;
+    }
+
+    /**
      * Returns the associated Declarations.
      * @return the Declarations instance
      */
Index: src/java/org/apache/fop/fo/FObj.java
===================================================================
--- src/java/org/apache/fop/fo/FObj.java	(revision 529569)
+++ src/java/org/apache/fop/fo/FObj.java	(working copy)
@@ -502,6 +502,11 @@
         extensionAttachments.add(attachment);
     }
     
+    /** @return whether this object has any extension attachments */
+    public boolean hasExtensionAttachments() {
+        return extensionAttachments != null && extensionAttachments.size() > 0;
+    }
+    
     /** @return the extension attachments of this FObj. */
     public List getExtensionAttachments() {
         if (extensionAttachments == null) {
Index: src/java/org/apache/fop/area/AreaTreeModel.java
===================================================================
--- src/java/org/apache/fop/area/AreaTreeModel.java	(revision 529569)
+++ src/java/org/apache/fop/area/AreaTreeModel.java	(working copy)
@@ -57,6 +57,7 @@
      * Start a page sequence on this model.
      * @param title the title of the new page sequence
      */
+    //public void startPageSequence(LineArea title) {
     public void startPageSequence(LineArea title) {
         currentPageSequence = new PageSequence(title);
         pageSequenceList.add(currentPageSequence);
Index: src/java/org/apache/fop/area/PageViewport.java
===================================================================
--- src/java/org/apache/fop/area/PageViewport.java	(revision 529569)
+++ src/java/org/apache/fop/area/PageViewport.java	(working copy)
@@ -82,10 +82,6 @@
     private Map markerLastEnd = null;
     private Map markerLastAny = null;
     
-    //Arbitrary attachments to the page from extensions that need to pass information
-    //down to the renderers.
-    private List extensionAttachments = null;
-
     /**
      * logging instance
      */
@@ -551,27 +547,7 @@
     public String getSimplePageMasterName() {
         return this.simplePageMasterName;
     }
-    
-    /**
-     * Adds a new ExtensionAttachment instance to this page.
-     * @param attachment the ExtensionAttachment
-     */
-    public void addExtensionAttachment(ExtensionAttachment attachment) {
-        if (this.extensionAttachments == null) {
-            this.extensionAttachments = new java.util.ArrayList();
-        }
-        extensionAttachments.add(attachment);
-    }
-    
-    /** @return the list of extension attachments for this page */
-    public List getExtensionAttachments() {
-        if (this.extensionAttachments == null) {
-            return Collections.EMPTY_LIST;
-        } else {
-            return this.extensionAttachments;
-        }
-    }
-    
+        
     /** @return True if this is a blank page. */
     public boolean isBlank() {
         return this.blank;
Index: src/java/org/apache/fop/area/AreaTreeObject.java
===================================================================
--- src/java/org/apache/fop/area/AreaTreeObject.java	(revision 529569)
+++ src/java/org/apache/fop/area/AreaTreeObject.java	(working copy)
@@ -21,8 +21,10 @@
 
 import java.util.Collections;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.fop.fo.extensions.ExtensionAttachment;
 import org.apache.fop.util.QName;
 
 /**
@@ -33,6 +35,10 @@
     /** Foreign attributes */
     protected Map foreignAttributes = null;
     
+    //Arbitrary attachments from extensions that need to pass information
+    //down to the renderers.
+    protected List extensionAttachments = null;
+
     /**
      * Sets a foreign attribute.
      * @param name the qualified name of the attribute
@@ -84,5 +90,28 @@
         }
     }
     
+    /** @return whether this page has any extension attachments */
+    public boolean hasExtensionAttachments() {
+        return (this.extensionAttachments != null && this.extensionAttachments.size() > 0);
+    }
     
+    /**
+     * Adds a new ExtensionAttachment instance to this page.
+     * @param attachment the ExtensionAttachment
+     */
+    public void addExtensionAttachment(ExtensionAttachment attachment) {
+        if (this.extensionAttachments == null) {
+            this.extensionAttachments = new java.util.ArrayList();
+        }
+        extensionAttachments.add(attachment);
+    }
+    
+    /** @return the list of extension attachments for this page */
+    public List getExtensionAttachments() {
+        if (this.extensionAttachments == null) {
+            return Collections.EMPTY_LIST;
+        } else {
+            return this.extensionAttachments;
+        }
+    }
 }
Index: src/java/org/apache/fop/area/RenderPagesModel.java
===================================================================
--- src/java/org/apache/fop/area/RenderPagesModel.java	(revision 529569)
+++ src/java/org/apache/fop/area/RenderPagesModel.java	(working copy)
@@ -52,9 +52,16 @@
      * Pages that have been prepared but not rendered yet.
      */
     protected List prepared = new java.util.ArrayList();
-    private List pendingODI = new java.util.ArrayList();
+    
+    private List afterPageODI = new java.util.ArrayList();
     private List endDocODI = new java.util.ArrayList();
-
+    private List beforePageSeqODI = new java.util.ArrayList();
+    private List afterPageSeqODI = new java.util.ArrayList();
+    private List beforePageODI = new java.util.ArrayList();
+    
+    /** we are processing the first page sequence */
+    private boolean firstPageSequence = true;
+    
     /**
      * Create a new render pages model with the given renderer.
      * @param userAgent FOUserAgent object for process
@@ -92,6 +99,20 @@
     public void startPageSequence(LineArea title) {
         super.startPageSequence(title);
         if (renderer.supportsOutOfOrder()) {
+            if (!firstPageSequence) {
+                // process off document items from after previous sequence
+                if (!afterPageSeqODI.isEmpty()) {
+                    processOffDocumentItems(afterPageSeqODI);
+                    afterPageSeqODI.clear();
+                }
+            } else {
+                firstPageSequence = false;
+            }
+            // process off document items from before this new sequence
+            if (!beforePageSeqODI.isEmpty()) {
+                processOffDocumentItems(beforePageSeqODI);
+                beforePageSeqODI.clear();
+            }
             renderer.startPageSequence(title);
         }
     }
@@ -113,9 +134,22 @@
         // could be referenced
         boolean ready = renderer.supportsOutOfOrder() && page.isResolved();
         if (ready) {
-            if (!renderer.supportsOutOfOrder() && page.getPageSequence().isFirstPage(page)) {
-                renderer.startPageSequence(this.currentPageSequence.getTitle());
+            /**
+             * commented below lines out as condition will never be satisfied
+             * as supportsOutOfOrder is always true at this point
+             */
+//            if (!renderer.supportsOutOfOrder() && page.getPageSequence().isFirstPage(page)) {
+//                renderer.startPageSequence(this.currentPageSequence);
+//                renderer.startPageSequence(this.currentPageSequence.getTitle());
+//            }
+            // check prepared pages
+            boolean renderPage = checkPreparedPages(page, false);
+
+            if (renderPage && !beforePageODI.isEmpty()) {
+                processOffDocumentItems(beforePageODI);
+                beforePageODI.clear();
             }
+
             try {
                 renderer.renderPage(page);
             } catch (RuntimeException re) {
@@ -127,46 +161,42 @@
                 throw new IllegalStateException("Fatal error occurred. Cannot continue. " 
                         + e.getClass().getName() + ": " + err);
             }
+            if (renderPage && !afterPageODI.isEmpty()) {
+                processOffDocumentItems(afterPageODI);
+                afterPageODI.clear();
+            }
+
             page.clear();
         } else {
             preparePage(page);
         }
-
-
-        // check prepared pages
-        boolean cont = checkPreparedPages(page, false);
-
-        if (cont) {
-            processOffDocumentItems(pendingODI);
-            pendingODI.clear();
-        }
     }
 
     /**
      * Check prepared pages
      *
-     * @param newpage the new page being added
+     * @param page the new page being added
      * @param renderUnresolved render pages with unresolved idref's
      *          (done at end-of-document processing)
      * @return true if the current page should be rendered
      *         false if the renderer doesn't support out of order
      *         rendering and there are pending pages
      */
-    protected boolean checkPreparedPages(PageViewport newpage, boolean
+    protected boolean checkPreparedPages(PageViewport page, boolean
         renderUnresolved) {
         for (Iterator iter = prepared.iterator(); iter.hasNext();) {
-            PageViewport p = (PageViewport)iter.next();
-            if (p.isResolved() || renderUnresolved) {
-                if (!renderer.supportsOutOfOrder() && p.getPageSequence().isFirstPage(p)) {
+            PageViewport pageViewport = (PageViewport)iter.next();
+            if (pageViewport.isResolved() || renderUnresolved) {
+                if (!renderer.supportsOutOfOrder() && pageViewport.getPageSequence().isFirstPage(pageViewport)) {
                     renderer.startPageSequence(this.currentPageSequence.getTitle());
                 }
                 try {
-                    renderer.renderPage(p);
-                    if (!p.isResolved()) {
-                        String[] idrefs = p.getIDRefs();
-                        for (int count = 0; count < idrefs.length; count++) {
-                            log.warn("Page " + p.getPageNumberString()
-                                + ": Unresolved id reference \"" + idrefs[count] 
+                    renderer.renderPage(pageViewport);
+                    if (!pageViewport.isResolved()) {
+                        String[] idrefs = pageViewport.getIDRefs();
+                        for (int i = 0; i < idrefs.length; i++) {
+                            log.warn("Page " + pageViewport.getPageNumberString()
+                                + ": Unresolved id reference \"" + idrefs[i] 
                                 + "\" found.");
                         }
                     }
@@ -174,7 +204,7 @@
                     // use error handler to handle this FOP or IO Exception
                     log.error(e);
                 }
-                p.clear();
+                pageViewport.clear();
                 iter.remove();
             } else {
                 // if keeping order then stop at first page not resolved
@@ -203,24 +233,39 @@
      * @see org.apache.fop.area.AreaTreeModel#handleOffDocumentItem(OffDocumentItem)
      */
     public void handleOffDocumentItem(OffDocumentItem oDI) {
-        switch(oDI.getWhenToProcess()) {
+        switch (oDI.getWhenToProcess()) {
             case OffDocumentItem.IMMEDIATELY:
                 renderer.processOffDocumentItem(oDI);
                 break;
+            case OffDocumentItem.BEFORE_PAGE_SEQ:
+                beforePageSeqODI.add(oDI);
+                break;
+            case OffDocumentItem.AFTER_PAGE_SEQ:
+                afterPageSeqODI.add(oDI);
+                break;
+            case OffDocumentItem.BEFORE_PAGE:
+                beforePageODI.add(oDI);
+                break;
             case OffDocumentItem.AFTER_PAGE:
-                pendingODI.add(oDI);
+                afterPageODI.add(oDI);
                 break;
             case OffDocumentItem.END_OF_DOC:
                 endDocODI.add(oDI);
                 break;
             default:
-                throw new RuntimeException();
+                throw new RuntimeException(oDI.getName() + " has an unknown whenToProcess value");
         }
     }
 
+    /**
+     * Iterates over list of off document items and tells the renderer
+     * to process them.
+     * 
+     * @param list
+     */
     private void processOffDocumentItems(List list) {
-        for (int count = 0; count < list.size(); count++) {
-            OffDocumentItem oDI = (OffDocumentItem)list.get(count);
+        for (int i = 0; i < list.size(); i++) {
+            OffDocumentItem oDI = (OffDocumentItem)list.get(i);
             renderer.processOffDocumentItem(oDI);
         }
     }
@@ -232,11 +277,23 @@
     public void endDocument() throws SAXException {
         // render any pages that had unresolved ids
         checkPreparedPages(null, true);
+        // process off document items from after previous sequence
 
-        processOffDocumentItems(pendingODI);
-        pendingODI.clear();
-        processOffDocumentItems(endDocODI);
+        if (!afterPageODI.isEmpty()) {
+            processOffDocumentItems(afterPageODI);
+            afterPageODI.clear();
+        }
 
+        if (!afterPageSeqODI.isEmpty()) {
+            processOffDocumentItems(afterPageSeqODI);
+            afterPageSeqODI.clear();
+        }
+
+        if (!endDocODI.isEmpty()) {
+            processOffDocumentItems(endDocODI);
+            endDocODI.clear();
+        }
+        
         try {
             renderer.stopRenderer();
         } catch (IOException ex) {
Index: src/java/org/apache/fop/area/OffDocumentItem.java
===================================================================
--- src/java/org/apache/fop/area/OffDocumentItem.java	(revision 529569)
+++ src/java/org/apache/fop/area/OffDocumentItem.java	(working copy)
@@ -23,7 +23,9 @@
  * Interface for objects that are processed by the renderer outside
  * of the actual document.
  * An object implementing this interface can be handled by the renderer according to these
- * possibilities: IMMEDIATELY, AFTER_PAGE or END_OF_DOC.
+ * 
+ * possibilities are: IMMEDIATELY, BEFORE_PAGE_SEQ, AFTER_PAGE_SEQ,
+ *                    BEFORE_PAGE, AFTER_PAGE or END_OF_DOC.
  */
 public interface OffDocumentItem {
 
@@ -34,21 +36,37 @@
     int IMMEDIATELY = 0;
 
     /**
+     * Process this extension before the next page sequence.
+     */
+    int BEFORE_PAGE_SEQ = 1;
+
+    /**
+     * Process this extension after the next page sequence.
+     */    
+    int AFTER_PAGE_SEQ = 2;
+
+    /**
+     * Process this extension before the next page is rendered
+     * or prepared when being handled by the area tree.
+     */
+    int BEFORE_PAGE = 3;
+
+    /**
      * Process this extension after the next page is rendered
      * or prepared when being handled by the area tree.
      */
-    int AFTER_PAGE = 1;
+    int AFTER_PAGE = 4;
 
     /**
      * Process this extension at the end of the document once
      * all pages have been fully rendered.
      */
-    int END_OF_DOC = 2;
+    int END_OF_DOC = 5;
 
     
     /**
      * Get an indicator of when this item should be processed
-     * @return int constant (IMMEDIATELY, AFTER_PAGE, END_OF_DOC)
+     * @return int constant (e.g. IMMEDIATELY, AFTER_PAGE, END_OF_DOC)
      */
     int getWhenToProcess();
 
Index: src/java/org/apache/fop/area/PageSequence.java
===================================================================
--- src/java/org/apache/fop/area/PageSequence.java	(revision 529569)
+++ src/java/org/apache/fop/area/PageSequence.java	(working copy)
@@ -24,11 +24,11 @@
 /**
  * Represents a page sequence in the area tree.
  */
-public class PageSequence {
+public class PageSequence extends AreaTreeObject {
 
     private List pages = new java.util.ArrayList();
     private LineArea title;
-    
+
     /**
      * Main constructor
      * @param title the title for the page-sequence, may be null
@@ -36,8 +36,15 @@
     public PageSequence(LineArea title) {
         this.title = title;
     }
-    
+
     /**
+     * sets the title of the page sequence
+     */
+    public void setTitle(LineArea title) {
+        this.title = title;
+    }
+
+    /**
      * @return the title of the page sequence in form of a line area, or null if there's no title
      */
     public LineArea getTitle() {
Index: src/java/org/apache/fop/area/CachedRenderPagesModel.java
===================================================================
--- src/java/org/apache/fop/area/CachedRenderPagesModel.java	(revision 529569)
+++ src/java/org/apache/fop/area/CachedRenderPagesModel.java	(working copy)
@@ -67,39 +67,39 @@
     /**
      * @see org.apache.fop.area.RenderPagesModel#checkPreparedPages(PageViewport, boolean)
      */
-    protected boolean checkPreparedPages(PageViewport newpage, boolean renderUnresolved) {
+    protected boolean checkPreparedPages(PageViewport page, boolean renderUnresolved) {
         for (Iterator iter = prepared.iterator(); iter.hasNext();) {
-            PageViewport p = (PageViewport)iter.next();
-            if (p.isResolved() || renderUnresolved) {
-                if (p != newpage) {
+            PageViewport pageViewport = (PageViewport)iter.next();
+            if (pageViewport.isResolved() || renderUnresolved) {
+                if (pageViewport != page) {
                     try {
                         // load page from cache
-                        String name = (String)pageMap.get(p);
+                        String name = (String)pageMap.get(pageViewport);
                         File tempFile = new File(baseDir, name);
                         log.debug("Loading page from: " + tempFile);
                         ObjectInputStream in = new ObjectInputStream(
                                              new BufferedInputStream(
                                                new FileInputStream(tempFile)));
                         try {
-                            p.loadPage(in);
+                            pageViewport.loadPage(in);
                         } finally {
                             IOUtils.closeQuietly(in);
                         }
                         if (!tempFile.delete()) {
                             log.warn("Temporary file could not be deleted: " + tempFile);
                         }
-                        pageMap.remove(p);
+                        pageMap.remove(pageViewport);
                     } catch (Exception e) {
                         log.error(e);
                     }
                 }
 
                 try {
-                    renderer.renderPage(p);
-                    if (!p.isResolved()) {
-                        String[] idrefs = p.getIDRefs();
+                    renderer.renderPage(pageViewport);
+                    if (!pageViewport.isResolved()) {
+                        String[] idrefs = pageViewport.getIDRefs();
                         for (int count = 0; count < idrefs.length; count++) {
-                            log.warn("Page " + p.getPageNumberString()
+                            log.warn("Page " + pageViewport.getPageNumberString()
                                 + ": Unresolved id reference \"" + idrefs[count] 
                                 + "\" found.");
                         }
@@ -108,7 +108,7 @@
                     // use error handler to handle this FOP or IO Exception
                     log.error(e);
                 }
-                p.clear();
+                pageViewport.clear();
                 iter.remove();
             } else {
                 if (!renderer.supportsOutOfOrder()) {
@@ -116,9 +116,9 @@
                 }
             }
         }
-        if (newpage != null && newpage.getPage() != null) {
-            savePage(newpage);
-            newpage.clear();
+        if (page != null && page.getPage() != null) {
+            savePage(page);
+            page.clear();
         }
         return renderer.supportsOutOfOrder() || prepared.isEmpty();
     }
Index: src/java/org/apache/fop/area/OffDocumentExtensionAttachment.java
===================================================================
--- src/java/org/apache/fop/area/OffDocumentExtensionAttachment.java	(revision 529569)
+++ src/java/org/apache/fop/area/OffDocumentExtensionAttachment.java	(working copy)
@@ -31,13 +31,28 @@
     private ExtensionAttachment attachment;
     
     /**
-     * Main constructor
+     * when to process this attachment
+     */
+    protected int whenToProcess;
+    
+    /**
+     * Default constructor
      * @param attachment the extension attachment to wrap.
      */
     public OffDocumentExtensionAttachment(ExtensionAttachment attachment) {
+        this(attachment, OffDocumentItem.IMMEDIATELY);
+    }
+
+    /**
+     * Constructor
+     * @param attachment the extension attachment to wrap.
+     * @param whenToProcess when to process this extension attachment
+     */
+    public OffDocumentExtensionAttachment(ExtensionAttachment attachment, int whenToProcess) {
         this.attachment = attachment;
+        this.whenToProcess = whenToProcess;
     }
-    
+
     /** @return the extension attachment. */
     public ExtensionAttachment getAttachment() {
         return this.attachment;
@@ -45,12 +60,19 @@
 
     /** @see org.apache.fop.area.OffDocumentItem#getWhenToProcess() */
     public int getWhenToProcess() {
-        return OffDocumentItem.IMMEDIATELY;
+        return this.whenToProcess;
     }
 
+    /**
+     * when the renderer should process this attachment
+     * @param whenToProcess
+     */
+    public void setWhenToProcess(int whenToProcess) {
+        this.whenToProcess = whenToProcess;
+    }
+
     /** @see org.apache.fop.area.OffDocumentItem#getName() */
     public String getName() {
         return attachment.getCategory();
     }
-    
 }
Index: src/java/org/apache/fop/area/AreaTreeHandler.java
===================================================================
--- src/java/org/apache/fop/area/AreaTreeHandler.java	(revision 529569)
+++ src/java/org/apache/fop/area/AreaTreeHandler.java	(working copy)
@@ -22,6 +22,7 @@
 // Java
 import java.io.OutputStream;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.HashMap;
@@ -41,8 +42,10 @@
 import org.apache.fop.datatypes.Numeric;
 import org.apache.fop.fo.FOEventHandler;
 import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.apache.fop.fo.pagination.LayoutMasterSet;
 import org.apache.fop.fo.pagination.PageSequence;
 import org.apache.fop.fo.pagination.Root;
+import org.apache.fop.fo.pagination.SimplePageMaster;
 import org.apache.fop.fo.pagination.bookmarks.BookmarkTree;
 import org.apache.fop.layoutmgr.PageSequenceLayoutManager;
 import org.apache.fop.layoutmgr.LayoutManagerMaker;
@@ -345,28 +348,63 @@
     /**
      * @see org.apache.fop.fo.FOEventHandler
      * @param pageSequence is the pageSequence being started 
-     * */
+     */
     public void startPageSequence(PageSequence pageSequence) {
         rootFObj = pageSequence.getRoot();
         finishPrevPageSequence(pageSequence.getInitialPageNumber());
         pageSequence.initPageNumber();
-        //extension attachments from fo:root
-        wrapAndAddExtensionAttachments(rootFObj.getExtensionAttachments());
+
         //extension attachments from fo:declarations
-        if (rootFObj.getDeclarations() != null) {
+        if (rootFObj.hasDeclarations() && rootFObj.getDeclarations().hasExtensionAttachments()) {
             wrapAndAddExtensionAttachments(rootFObj.getDeclarations().getExtensionAttachments());
         }
+
+        //extension attachments from page sequence referenced fo:simple-page-master
+        String masterRef = pageSequence.getMasterReference();
+        if (masterRef != null) {
+            LayoutMasterSet layoutMasterSet = rootFObj.getLayoutMasterSet();
+            if (layoutMasterSet != null) {
+                SimplePageMaster simplePageMaster = layoutMasterSet.getSimplePageMaster(masterRef);
+                if (simplePageMaster != null && simplePageMaster.hasExtensionAttachments()) {
+                    wrapAndAddExtensionAttachments(OffDocumentItem.BEFORE_PAGE_SEQ,
+                            simplePageMaster.getExtensionAttachments());
+                }
+            }
+        }
+
+        //extension attachments from fo:root
+        if (rootFObj.hasExtensionAttachments()) {
+            wrapAndAddExtensionAttachments(rootFObj.getExtensionAttachments());
+        }        
     }
     
-    private void wrapAndAddExtensionAttachments(List list) {
-        Iterator i = list.iterator();
-        while (i.hasNext()) {
-            ExtensionAttachment attachment = (ExtensionAttachment)i.next();
-            addOffDocumentItem(new OffDocumentExtensionAttachment(attachment));
-        }
+    /**
+     * takes collection of extension attachments and
+     * adds off document extension attachment
+     * 
+     * @param whenToProcess off document item
+     * @param collection
+     */
+    private void wrapAndAddExtensionAttachments(int whenToProcessODI, Collection collection) {
+        Iterator iter = collection.iterator();
+        while (iter.hasNext()) {
+            ExtensionAttachment attachment = (ExtensionAttachment)iter.next();
+            addOffDocumentItem(new OffDocumentExtensionAttachment(attachment, whenToProcessODI));
+        }        
     }
     
     /**
+     * takes collection of extension attachments and
+     * adds off document extension attachment
+     * default: to be processed immediately by the renderer.
+     * 
+     * @param collection
+     */
+    private void wrapAndAddExtensionAttachments(Collection collection) {
+        wrapAndAddExtensionAttachments(OffDocumentItem.IMMEDIATELY, collection);
+    }
+    
+    /**
      * End the PageSequence.
      * The PageSequence formats Pages and adds them to the AreaTree.
      * The area tree then handles what happens with the pages.
@@ -377,14 +415,17 @@
 
         if (outputStatistics) {
             long memoryNow = runtime.totalMemory() - runtime.freeMemory();
-            log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
+            if (log.isDebugEnabled()) {
+                log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
+            }
         }
 
         // If no main flow, nothing to layout!
         if (pageSequence.getMainFlow() != null) {
             PageSequenceLayoutManager pageSLM;
             pageSLM = getLayoutManagerMaker().makePageSequenceLayoutManager(
-                    this, pageSequence);
+                    this, 
+                    pageSequence);
             pageSLM.activateLayout();
             // preserve the current PageSequenceLayoutManger for the
             // force-page-count check at the beginning of the next PageSequence
@@ -418,7 +459,7 @@
         // process fox:destination elements
         ArrayList destinationList = rootFObj.getDestinationList();
         if (destinationList != null) {
-            while(destinationList.size() > 0) {
+            while (destinationList.size() > 0) {
                 Destination destination = (Destination)destinationList.remove(0);
                 DestinationData destinationData = new DestinationData(destination);
                 addOffDocumentItem(destinationData);
@@ -442,15 +483,19 @@
             long memoryUsed = (memoryNow - initialMemory) / 1024L;
             long timeUsed = System.currentTimeMillis() - startTime;
             int pageCount = rootFObj.getTotalPagesGenerated();
-            log.debug("Initial heap size: " + (initialMemory / 1024L) + "Kb");
-            log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
-            log.debug("Total memory used: " + memoryUsed + "Kb");
-            log.debug("Total time used: " + timeUsed + "ms");
-            log.debug("Pages rendered: " + pageCount);
+            if (log.isDebugEnabled()) {
+                log.debug("Initial heap size: " + (initialMemory / 1024L) + "Kb");
+                log.debug("Current heap size: " + (memoryNow / 1024L) + "Kb");
+                log.debug("Total memory used: " + memoryUsed + "Kb");
+                log.debug("Total time used: " + timeUsed + "ms");
+                log.debug("Pages rendered: " + pageCount);
+            }
             if (pageCount > 0) {
                 long perPage = (timeUsed / pageCount);
                 long ppm = (timeUsed != 0 ? Math.round(60000 * pageCount / (double)timeUsed) : -1);
-                log.debug("Avg render time: " + perPage + "ms/page (" + ppm + "pages/min)");
+                if (log.isDebugEnabled()) {
+                    log.debug("Avg render time: " + perPage + "ms/page (" + ppm + "pages/min)");
+                }
             }
         }
     }
