]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Bugzilla #42144
authorChris Bowditch <cbowditch@apache.org>
Thu, 4 Oct 2007 13:51:25 +0000 (13:51 +0000)
committerChris Bowditch <cbowditch@apache.org>
Thu, 4 Oct 2007 13:51:25 +0000 (13:51 +0000)
Safely set postscript page device dictionary
Submitted by Adrian Cumiskey <dev.at.cumiskey.com>

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

14 files changed:
src/documentation/content/xdocs/trunk/output.xml
src/java/org/apache/fop/area/PageViewport.java
src/java/org/apache/fop/render/ps/PSDictionary.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSDictionaryFormatException.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSPageDeviceDictionary.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/PSRenderer.java
src/java/org/apache/fop/render/ps/PSRendererConfigurator.java
src/java/org/apache/fop/render/ps/extensions/PSExtensionAttachment.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/extensions/PSExtensionElementMapping.java
src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java
src/java/org/apache/fop/render/ps/extensions/PSSetPageDevice.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java [new file with mode: 0644]
src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java
test/layoutengine/standard-testcases/ps-extension_2.xml [new file with mode: 0644]

index 21743519f122f44686680943218a3e532c8ca640..798c61811c799e15986c1497e6b21ae512390aed 100644 (file)
@@ -212,6 +212,8 @@ out = proc.getOutputStream();]]></source>
   <auto-rotate-landscape>false</auto-rotate-landscape>
   <language-level>3</language-level>
   <optimize-resources>false</optimize-resources>
+  <safe-set-page-device>false</safe-set-page-device>
+  <dsc-compliant>true</dsc-compliant>
 </renderer>]]></source>
       <p>
         The default value for the "auto-rotate-landscape" setting is "false". Setting it
@@ -230,6 +232,20 @@ out = proc.getOutputStream();]]></source>
         reduce file size but can potentially increase the memory needed in the interpreter
         to process.
       </p>
+      <p>
+        The default value for the "safe-set-page-device" setting is "false". Setting it
+        to "true" will cause the renderer to invoke a postscript macro which guards against
+        the possibility of invalid/unsupported postscript key/values being issued to the
+        implementing postscript page device. 
+      </p>
+      <p>
+        The default value for the "dsc-compliant" setting is "true". Setting it
+        to "false" will break DSC compliance by minimizing the number of setpagedevice
+        calls in the postscript document output.  This feature may be useful when unwanted
+        blank pages are experienced in your postscript output.  This problem is caused by
+        the particular postscript implementation issuing unwanted postscript subsystem
+        initgraphics/erasepage calls on each setpagedevice call.
+      </p>
     </section>
   <section id="ps-limitations">
     <title>Limitations</title>
@@ -820,3 +836,4 @@ out = proc.getOutputStream();]]></source>
   </body>
 </document>
 
+
index 184eb9d3257714605278cc952a69fcfcfb5c3bbb..af557ade9385725e7e2840e7c8eb778b907f3c72 100644 (file)
@@ -666,4 +666,9 @@ public class PageViewport extends AreaTreeObject implements Resolvable, Cloneabl
     public RegionReference getRegionReference(int id) {
         return getPage().getRegionViewport(id).getRegionReference();
     }
+    
+    /** @return whether this page viewport has any extension attachments */
+    public boolean hasExtensionAttachments() {
+        return this.extensionAttachments != null && !this.extensionAttachments.isEmpty();
+    }
 }
diff --git a/src/java/org/apache/fop/render/ps/PSDictionary.java b/src/java/org/apache/fop/render/ps/PSDictionary.java
new file mode 100644 (file)
index 0000000..dbf7173
--- /dev/null
@@ -0,0 +1,312 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ * \r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/* $Id: $ */\r
+\r
+package org.apache.fop.render.ps;\r
+\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.StringTokenizer;\r
+\r
+/**\r
+ * This class is used to encapsulate postscript dictionary objects.\r
+ */\r
+public class PSDictionary extends java.util.HashMap {\r
+    \r
+    private static final long serialVersionUID = 815367222496219197L;\r
+\r
+    /**\r
+     * This class is used to parse dictionary strings.\r
+     */\r
+    private static class Maker {\r
+        \r
+        /**\r
+         * Simple token holding class\r
+         */\r
+        private class Token {\r
+            /**\r
+             * start index in string\r
+             */\r
+            private int startIndex = -1;\r
+            \r
+            /**\r
+             * end index in string\r
+             */\r
+            private int endIndex = -1;\r
+            \r
+            /**\r
+             * token string value\r
+             */\r
+            private String value;        \r
+        }\r
+        \r
+        private static final String[][] BRACES = {\r
+            {"<<", ">>"},\r
+            {"[", "]"},\r
+            {"{", "}"}\r
+        };\r
+\r
+        private static final int OPENING = 0;\r
+        private static final int CLOSING = 1;\r
+        private static final int DICTIONARY = 0;\r
+        private static final int ARRAY = 1;\r
+        private static final int PROCEDURE = 2;\r
+\r
+        /**\r
+         * Returns a Token containing the start, end index and value of the next token\r
+         * found in a given string\r
+         * \r
+         * @param str\r
+         *            string to search\r
+         * @param fromIndex\r
+         *            search from index\r
+         * @return Token containing the start, end index and value of the next token\r
+         */\r
+        protected Token nextToken(String str, int fromIndex) {\r
+            Token t = null;\r
+            for (int i = fromIndex; i < str.length(); i++) {\r
+                boolean isWhitespace = Character.isWhitespace(str.charAt(i));\r
+                // start index found\r
+                if (t == null && !isWhitespace) {\r
+                    t = new Token();\r
+                    t.startIndex = i;\r
+                // end index found\r
+                } else if (t != null && isWhitespace) {\r
+                    t.endIndex = i;\r
+                    break;\r
+                }\r
+            }\r
+            // start index found\r
+            if (t != null) {\r
+                // end index not found so take end of string\r
+                if (t.endIndex == -1) {\r
+                    t.endIndex = str.length();\r
+                }\r
+                t.value = str.substring(t.startIndex, t.endIndex);\r
+            }\r
+            return t;\r
+        }\r
+\r
+        /**\r
+         * Returns the closing brace index from a given string searches from a\r
+         * given index\r
+         * \r
+         * @param str\r
+         *            string to search\r
+         * @param braces\r
+         *            string array of opening and closing brace\r
+         * @param fromIndex\r
+         *            searches from index\r
+         * @return matching brace index\r
+         * @throws org.apache.fop.render.ps.PSDictionaryFormatException\r
+         *            thrown in the event that a parsing error occurred\r
+         */\r
+        private int indexOfMatchingBrace(String str, String[] braces,\r
+                int fromIndex) throws PSDictionaryFormatException {\r
+            final int len = str.length();\r
+            if (braces.length != 2) {\r
+                throw new PSDictionaryFormatException("Wrong number of braces");\r
+            }\r
+            for (int openCnt = 0, closeCnt = 0; fromIndex < len; fromIndex++) {\r
+                if (str.startsWith(braces[OPENING], fromIndex)) {\r
+                    openCnt++;\r
+                } else if (str.startsWith(braces[CLOSING], fromIndex)) {\r
+                    closeCnt++;\r
+                    if (openCnt > 0 && openCnt == closeCnt) {\r
+                        return fromIndex; // found\r
+                    }\r
+                }\r
+            }\r
+            return -1; // not found\r
+        }\r
+\r
+        /**\r
+         * Strips braces from complex object string\r
+         * \r
+         * @param str\r
+         *            String to parse\r
+         * @param braces\r
+         *            String array containing opening and closing braces\r
+         * @return String with braces stripped\r
+         * @throws\r
+         *      org.apache.fop.render.ps.PSDictionaryFormatException object format exception\r
+         */\r
+        private String stripBraces(String str, String[] braces) throws PSDictionaryFormatException {\r
+            // find first opening brace\r
+            int firstIndex = str.indexOf(braces[OPENING]);\r
+            if (firstIndex == -1) {\r
+                throw new PSDictionaryFormatException(\r
+                        "Failed to find opening parameter '" + braces[OPENING]\r
+                                + "");\r
+            }\r
+\r
+            // find last matching brace\r
+            int lastIndex = indexOfMatchingBrace(str, braces, firstIndex);\r
+            if (lastIndex == -1) {\r
+                throw new PSDictionaryFormatException(\r
+                        "Failed to find matching closing parameter '"\r
+                                + braces[CLOSING] + "'");\r
+            }\r
+\r
+            // strip brace and trim\r
+            int braceLen = braces[OPENING].length();\r
+            str = str.substring(firstIndex + braceLen, lastIndex).trim();\r
+            return str;\r
+        }\r
+\r
+        /**\r
+         * Parses a dictionary string and provides a dictionary object\r
+         * \r
+         * @param str a dictionary string\r
+         * @return A postscript dictionary object\r
+         * @throws\r
+         *      PSDictionaryFormatException thrown if a dictionary format exception occurs\r
+         */\r
+        public PSDictionary parseDictionary(String str) throws PSDictionaryFormatException {\r
+            PSDictionary dictionary = new PSDictionary();\r
+            str = stripBraces(str.trim(), BRACES[DICTIONARY]);\r
+            // length of dictionary string\r
+            final int len = str.length();\r
+\r
+            Token keyToken;\r
+            for (int currIndex = 0; (keyToken = nextToken(str, currIndex)) != null\r
+                    && currIndex <= len;) {\r
+                if (keyToken.value == null) {\r
+                    throw new PSDictionaryFormatException("Failed to parse object key");\r
+                }\r
+                Token valueToken = nextToken(str, keyToken.endIndex + 1);\r
+                String[] braces = null;\r
+                for (int i = 0; i < BRACES.length; i++) {\r
+                    if (valueToken.value.startsWith(BRACES[i][OPENING])) {\r
+                        braces = BRACES[i];\r
+                        break;\r
+                    }\r
+                }\r
+                Object obj = null;\r
+                if (braces != null) {\r
+                    // find closing brace\r
+                    valueToken.endIndex = indexOfMatchingBrace(str, braces,\r
+                        valueToken.startIndex)\r
+                        + braces[OPENING].length();\r
+                    if (valueToken.endIndex < 0) {\r
+                        throw new PSDictionaryFormatException("Closing value brace '"\r
+                            + braces[CLOSING] + "' not found for key '"\r
+                            + keyToken.value + "'");\r
+                    }\r
+                    valueToken.value = str.substring(valueToken.startIndex, valueToken.endIndex);\r
+                }\r
+                if (braces == null || braces == BRACES[PROCEDURE]) {\r
+                    obj = valueToken.value;                        \r
+                } else if (BRACES[ARRAY] == braces) {\r
+                    List objList = new java.util.ArrayList();\r
+                    String objString = stripBraces(valueToken.value, braces);\r
+                    StringTokenizer tokenizer = new StringTokenizer(objString, ",");\r
+                    while (tokenizer.hasMoreTokens()) {\r
+                        objList.add(tokenizer.nextToken());\r
+                    }\r
+                    obj = objList;                        \r
+                } else if (BRACES[DICTIONARY] == braces) {\r
+                    obj = parseDictionary(valueToken.value);\r
+                }\r
+                dictionary.put(keyToken.value, obj);\r
+                currIndex = valueToken.endIndex + 1;\r
+            }\r
+            return dictionary;\r
+        }    \r
+    }\r
+    \r
+    /**\r
+     * Parses a given a dictionary string and returns an object \r
+     * \r
+     * @param str dictionary string\r
+     * @return dictionary object\r
+     * @throws PSDictionaryFormatException object format exception\r
+     */\r
+    public static PSDictionary valueOf(String str) throws PSDictionaryFormatException {\r
+        return (new Maker()).parseDictionary(str);\r
+    }\r
+\r
+    /**\r
+     * @param obj object to test equality against\r
+     * @return whether a given object is equal to this dictionary object\r
+     * @see java.lang.Object#equals(Object)\r
+     */\r
+    public boolean equals(Object obj) {\r
+        if (!(obj instanceof PSPageDeviceDictionary)) {\r
+            return false;\r
+        }\r
+        PSDictionary dictionaryObj = (PSDictionary) obj;\r
+        if (dictionaryObj.size() != size()) {\r
+            return false;\r
+        }\r
+        for (Iterator it = keySet().iterator(); it.hasNext();) {\r
+            String key = (String) it.next();\r
+            if (!dictionaryObj.containsKey(key)) {\r
+                return false;\r
+            }\r
+            if (!dictionaryObj.get(key).equals(get(key))) {\r
+                return false;\r
+            }\r
+        }\r
+        return true;\r
+    }\r
+    \r
+    /**\r
+     * @return a hash code value for this object.\r
+     * @see java.lang.Object#hashCode()\r
+     */\r
+    public int hashCode() {\r
+        int hashCode = 7;\r
+        for (Iterator it = values().iterator(); it.hasNext();) {\r
+            Object value = it.next();\r
+            hashCode += value.hashCode();\r
+        }\r
+        return hashCode;\r
+    }\r
+\r
+    /**\r
+     * @return a string representation of this dictionary\r
+     * @see java.lang.String#toString()\r
+     */\r
+    public String toString() {\r
+        if (isEmpty()) {\r
+            return "";\r
+        }\r
+        StringBuffer sb = new StringBuffer("<<\n");\r
+        for (Iterator it = super.keySet().iterator(); it.hasNext();) {\r
+            String key = (String) it.next();\r
+            sb.append("  " + key + " ");\r
+            Object obj = super.get(key);\r
+            if (obj instanceof java.util.ArrayList) {\r
+                List array = (List)obj;\r
+                String str = "[";\r
+                for (int i = 0; i < array.size(); i++) {\r
+                    Object element = array.get(i);\r
+                    str += element + " ";\r
+                }\r
+                str = str.trim();\r
+                str += "]";\r
+                sb.append(str + "\n");                \r
+            } else {\r
+                sb.append(obj.toString() + "\n");\r
+            }\r
+        }\r
+        sb.append(">>");\r
+        return sb.toString();\r
+    }\r
+}\r
diff --git a/src/java/org/apache/fop/render/ps/PSDictionaryFormatException.java b/src/java/org/apache/fop/render/ps/PSDictionaryFormatException.java
new file mode 100644 (file)
index 0000000..2153e81
--- /dev/null
@@ -0,0 +1,37 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ * \r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/* $Id: $ */\r
+\r
+package org.apache.fop.render.ps;\r
+\r
+/**\r
+ * Thrown to indicate that a formatting error has occured when\r
+ * trying to parse a postscript dictionary object\r
+ */\r
+public class PSDictionaryFormatException extends Exception {\r
+\r
+    private static final long serialVersionUID = 6492321557297860931L;\r
+\r
+    /**\r
+     * Default constructor\r
+     * @param string error message\r
+     */\r
+    public PSDictionaryFormatException(String string) {\r
+        super(string);\r
+    }\r
+}\r
diff --git a/src/java/org/apache/fop/render/ps/PSPageDeviceDictionary.java b/src/java/org/apache/fop/render/ps/PSPageDeviceDictionary.java
new file mode 100644 (file)
index 0000000..c327423
--- /dev/null
@@ -0,0 +1,110 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ * \r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ * \r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/* $Id: $ */\r
+\r
+package org.apache.fop.render.ps;\r
+\r
+/**\r
+ * Postscript page device dictionary object\r
+ * \r
+ * This object is used by the postscript renderer to hold postscript page device\r
+ * values.  It can also be used to minimize the number of setpagedevice calls when\r
+ * DSC compliance is false.\r
+ */\r
+public class PSPageDeviceDictionary extends PSDictionary {\r
+    \r
+    private static final long serialVersionUID = 845943256485806509L;\r
+\r
+    /**\r
+     * Whether or not the contents of the dictionary are flushed on retrieval\r
+     */\r
+    private boolean flushOnRetrieval = false;\r
+\r
+    /**\r
+     * Dictionary content that has not been output/written yet\r
+     */\r
+    private PSDictionary unRetrievedContentDictionary;\r
+\r
+    /**\r
+     * @param key key with which the specified value is to be associated.\r
+     * @param value value to be associated with the specified key.\r
+     * @return the previous value associated with the key or null\r
+     * @see java.util.Map#put(Object, Object)\r
+     */\r
+    public Object put(Object key, Object value) {\r
+        Object previousValue = super.put(key, value);\r
+        if (flushOnRetrieval) {\r
+            if (previousValue == null || !previousValue.equals(value)) {\r
+                unRetrievedContentDictionary.put(key, value);\r
+            }\r
+        }\r
+        return previousValue;\r
+    }\r
+\r
+    /**\r
+     * @see java.util.Map#clear()\r
+     */\r
+    public void clear() {\r
+        super.clear();\r
+        if (unRetrievedContentDictionary != null) {\r
+            unRetrievedContentDictionary.clear();\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Returns <tt>true</tt> if this map contains no key-value mappings.\r
+     *\r
+     * @return <tt>true</tt> if this map contains no key-value mappings.\r
+     */\r
+    public boolean isEmpty() {\r
+        if (flushOnRetrieval) {\r
+            return unRetrievedContentDictionary.isEmpty();\r
+        }\r
+        return super.isEmpty();\r
+    }\r
+\r
+    /**\r
+     * The contents of the dictionary are flushed when written\r
+     * @param flushOnRetrieval boolean value\r
+     */\r
+    public void setFlushOnRetrieval(boolean flushOnRetrieval) {\r
+        this.flushOnRetrieval = flushOnRetrieval;\r
+        if (flushOnRetrieval) {\r
+            unRetrievedContentDictionary = new PSDictionary();\r
+        }\r
+    }\r
+    \r
+    /**\r
+     * Returns a dictionary string with containing all unwritten content note:\r
+     * unnecessary writes are important as there is a device specific\r
+     * initgraphics call by the underlying postscript interpreter on every\r
+     * setpagedevice call which can result in blank pages etc.\r
+     * \r
+     * @return unwritten content dictionary string\r
+     */\r
+    public String getContent() {\r
+        String content;\r
+        if (flushOnRetrieval) {\r
+            content = unRetrievedContentDictionary.toString();\r
+            unRetrievedContentDictionary.clear();\r
+        } else {\r
+            content = super.toString();\r
+        }\r
+        return content;\r
+    }    \r
+}\r
index bb77dd55a4412fd3bc1c21d1e98201e3803eaea8..5d823e86ea179d8498c705c28bc3caca3bf9707f 100644 (file)
@@ -28,6 +28,7 @@ import java.io.IOException;
 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;
@@ -69,6 +70,8 @@ import org.apache.fop.render.Graphics2DAdapter;
 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.PSSetPageDevice;
 import org.apache.fop.render.ps.extensions.PSSetupCode;
 import org.apache.fop.util.CharUtilities;
 
@@ -141,7 +144,16 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda
     private Map fontResources;
     /** This is a map of PSResource instances of all forms (key: uri) */
     private Map formResources;
-    
+
+    /** encapsulation of dictionary used in setpagedevice instruction **/
+    private PSPageDeviceDictionary pageDeviceDictionary;
+
+    /** Whether or not the safe set page device macro will be used or not */
+    private boolean safeSetPageDevice = false;
+
+    /** Whether or not Dublin Core Standard (dsc) compliant output is enforced */
+    private boolean dscCompliant = true;
+
     /**
      * {@inheritDoc}
      */
@@ -713,6 +725,11 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda
         this.gen.setPSLevel(this.languageLevel);
         this.currentPageNumber = 0;
 
+        //Initial default page device dictionary settings
+        this.pageDeviceDictionary = new PSPageDeviceDictionary();
+        pageDeviceDictionary.setFlushOnRetrieval(!this.dscCompliant);
+        pageDeviceDictionary.put("/ImagingBBox", "null");
+
         //PostScript Header
         writeln(DSCConstants.PS_ADOBE_30);
         gen.writeDSCComment(DSCConstants.CREATOR, new String[] {userAgent.getProducer()});
@@ -755,6 +772,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda
             IOUtils.closeQuietly(gen.getOutputStream());
             rewritePostScriptFile();
         }
+        this.pageDeviceDictionary.clear();
     }
     
     /**
@@ -796,12 +814,34 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda
         }
         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) {
+                if (PSExtensionAttachment.CATEGORY.equals(attachment.getCategory())) {
+                    if (attachment instanceof PSSetupCode) {
+                        if (setupCodeList == null) {
+                            setupCodeList = new java.util.ArrayList();
+                        }
+                        if (!setupCodeList.contains(attachment)) {
+                            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 {
+                                this.pageDeviceDictionary.putAll(PSDictionary.valueOf(content));
+                            } catch (PSDictionaryFormatException e) {
+                                log.error("Failed to parse dictionary string: "
+                                        + e.getMessage() + ", content = '" + content + "'");
+                            }
+                        }
+                    }        
                 }
-                setupCodeList.add(setupCode);
             }
         }
         super.processOffDocumentItem(oDI);
@@ -872,91 +912,114 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda
         log.debug("renderPage(): " + 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;
+
+        double pageWidth = Math.round(page.getViewArea().getWidth()) / 1000f;
+        double pageHeight = Math.round(page.getViewArea().getHeight()) / 1000f;
         boolean rotate = false;
-        if (this.autoRotateLandscape && (pageheight < pagewidth)) {
+        List pageSizes = new java.util.ArrayList();
+        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)});
+            pageSizes.add(new Long(Math.round(pageHeight)));
+            pageSizes.add(new Long(Math.round(pageWidth)));
+        } else {
+            pageSizes.add(new Long(Math.round(pageWidth)));
+            pageSizes.add(new Long(Math.round(pageHeight)));
+        }
+        pageDeviceDictionary.put("/PageSize", pageSizes);
+        
+        if (page.hasExtensionAttachments()) {
+            for (Iterator iter = page.getExtensionAttachments().iterator();
+                iter.hasNext();) {
+                ExtensionAttachment attachment = (ExtensionAttachment) iter.next();
+                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 {
+                            pageDeviceDictionary.putAll(PSDictionary.valueOf(content));
+                        } catch (PSDictionaryFormatException e) {
+                            log.error("failed to parse dictionary string: "
+                                    + e.getMessage() + ", [" + content + "]");
+                        }
+                    }
+                }
+            }
+        }
+
+        try {
+            if (setupCodeList != null) {
+                writeEnclosedExtensionAttachments(setupCodeList);
+                setupCodeList.clear();
+            }
+        } catch (IOException e) {
+            log.error(e.getMessage());
+        }
+        final Integer zero = new Integer(0);
+        if (rotate) {
+            gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[] {
+                    zero, zero, new Long(Math.round(pageHeight)),
+                    new Long(Math.round(pageWidth)) });
+            gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[] {
+                    zero, zero, new Double(pageHeight),
+                    new Double(pageWidth) });
             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");
+            gen.writeDSCComment(DSCConstants.PAGE_BBOX, new Object[] {
+                    zero, zero, new Long(Math.round(pageWidth)),
+                    new Long(Math.round(pageHeight)) });
+            gen.writeDSCComment(DSCConstants.PAGE_HIRES_BBOX, new Object[] {
+                    zero, zero, new Double(pageWidth),
+                    new Double(pageHeight) });
+            if (autoRotateLandscape) {
+                gen.writeDSCComment(DSCConstants.PAGE_ORIENTATION,
+                        "Portrait");
             }
         }
-        gen.writeDSCComment(DSCConstants.PAGE_RESOURCES, 
+        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);
-                }
+
+        // Write any unwritten changes to page device dictionary
+        if (!pageDeviceDictionary.isEmpty()) {
+            String content = pageDeviceDictionary.getContent();
+            if (safeSetPageDevice) {
+                content += " SSPD";
+            } else {
+                content += " setpagedevice"; 
             }
-            writeSetupCodeList(list, "PageSetupCode");
+            writeEnclosedExtensionAttachment(new PSSetPageDevice(content));
         }
-        
+
         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(Math.round(pageHeight) + " 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);
-
-        gen.writeDSCComment(DSCConstants.END_PAGE_SETUP);
+        concatMatrix(1, 0, 0, -1, 0, pageHeight);
 
+        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);
     }
 
@@ -1259,7 +1322,6 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda
 
     /**
      * {@inheritDoc}
-     *          int, int, int, int, java.util.Map)
      */
     protected RendererContext createRendererContext(int x, int y, int width, int height, 
             Map foreignAttributes) {
@@ -1275,5 +1337,84 @@ public class PSRenderer extends AbstractPathOrientedRenderer implements ImageAda
         return MIME_TYPE;
     }
 
+    /**
+     * Formats and writes a PSExtensionAttachment to the output stream.
+     * 
+     * @param attachment an PSExtensionAttachment instance
+     */
+    private void writeEnclosedExtensionAttachment(PSExtensionAttachment attachment)
+            throws IOException {
+        String info = "";
+        if (attachment instanceof PSSetupCode) {
+            PSSetupCode setupCodeAttach = (PSSetupCode)attachment;
+            String name = setupCodeAttach.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);
+    }
+
+    /**
+     * Formats and writes a Collection of PSExtensionAttachment instances to
+     * the output stream.
+     * 
+     * @param attachmentCollection
+     *            a Collection of PSExtensionAttachment instances
+     */
+    private void writeEnclosedExtensionAttachments(Collection attachmentCollection)
+            throws IOException {
+        Iterator iter = attachmentCollection.iterator();
+        while (iter.hasNext()) {
+            PSExtensionAttachment attachment = (PSExtensionAttachment)iter
+                    .next();
+            if (attachment != null) {
+                writeEnclosedExtensionAttachment(attachment);
+            }
+            iter.remove();
+        }
+    }
 
+    /**
+     * Sets whether or not the safe set page device macro should be used
+     * (as opposed to directly invoking setpagedevice) when setting the
+     * postscript page device.
+     * 
+     * This option is a useful option when you want to guard against the possibility
+     * of invalid/unsupported postscript key/values being placed in the page device. 
+     * 
+     * @param safeSetPageDevice setting to false and the renderer will make a
+     * standard "setpagedevice" call, setting to true will make a safe set page
+     * device macro call (default is false).
+     */
+    public void setSafeSetPageDevice(boolean safeSetPageDevice) {
+        this.safeSetPageDevice = safeSetPageDevice;
+    }
+
+    /**
+     * Sets whether or not Dublin Core Standard (dsc) compliance is enforced.
+     * 
+     * It can cause problems (unwanted postscript subsystem initgraphics/erasepage calls)
+     * on some printers when the pagedevice is set.  If this causes problems on a
+     * particular implementation then use this setting with a 'false' value to try and
+     * minimize the number of setpagedevice calls in the postscript document output. 
+     * 
+     * Set this value to false if you experience unwanted blank pages in your
+     * postscript output.
+     * @param dscCompliant boolean value (default is true)
+     */
+    public void setDSCCompliant(boolean dscCompliant) {
+        this.dscCompliant = dscCompliant;        
+    }
 }
index c7b5a025b3a4c43957d4f0efd5237071f702d712..6caa92c8584a403692eb56b2ee04ad17fda8a27c 100644 (file)
@@ -49,8 +49,13 @@ public class PSRendererConfigurator extends PrintRendererConfigurator {
             super.configure(renderer);
 
             PSRenderer psRenderer = (PSRenderer)renderer;
+            
             psRenderer.setAutoRotateLandscape(
                 cfg.getChild("auto-rotate-landscape").getValueAsBoolean(false));
+            psRenderer.setSafeSetPageDevice(
+                cfg.getChild("safe-set-page-device").getValueAsBoolean(false));
+            psRenderer.setDSCCompliant(
+                cfg.getChild("dsc-compliant").getValueAsBoolean(true));
         }
     }
 }
diff --git a/src/java/org/apache/fop/render/ps/extensions/PSExtensionAttachment.java b/src/java/org/apache/fop/render/ps/extensions/PSExtensionAttachment.java
new file mode 100644 (file)
index 0000000..0fb623b
--- /dev/null
@@ -0,0 +1,108 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/* $Id: $ */\r
+\r
+package org.apache.fop.render.ps.extensions;\r
+\r
+import org.apache.fop.fo.extensions.ExtensionAttachment;\r
+import org.apache.fop.util.XMLizable;\r
+import org.xml.sax.ContentHandler;\r
+import org.xml.sax.SAXException;\r
+import org.xml.sax.helpers.AttributesImpl;\r
+\r
+/**\r
+ * This is the pass-through value object for the PostScript extension.\r
+ */\r
+public abstract class PSExtensionAttachment implements ExtensionAttachment, XMLizable {\r
+    \r
+    /** extension node content */\r
+    protected String content;\r
+\r
+    /** The category URI for this extension attachment. */\r
+    public static final String CATEGORY = "apache:fop:extensions:postscript";\r
+\r
+    /**\r
+     * Default constructor.\r
+     * @param content the content of the setup code object\r
+     */\r
+    public PSExtensionAttachment(String content) {\r
+        this.content = content;\r
+    }\r
+\r
+    /**\r
+     * No-argument contructor.\r
+     */\r
+    public PSExtensionAttachment() {\r
+    }\r
+\r
+    /**\r
+     * @return the category URI\r
+     * @see org.apache.fop.fo.extensions.ExtensionAttachment#getCategory()\r
+     */\r
+    public String getCategory() {\r
+        return CATEGORY;\r
+    }\r
+    \r
+    /** @return the content */\r
+    public String getContent() {\r
+        return content;\r
+    }\r
+    \r
+    /**\r
+     * Sets the content for the setup code object.\r
+     * @param content The content to set.\r
+     */\r
+    public void setContent(String content) {\r
+        this.content = content;\r
+    }\r
+           \r
+    /**\r
+     * Generates SAX events representing the object's state.\r
+     * \r
+     * @param handler ContentHandler instance to send the SAX events to\r
+     * @throws SAXException if there's a problem generating the SAX events\r
+     * @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler)\r
+     */\r
+    public void toSAX(ContentHandler handler) throws SAXException {\r
+        AttributesImpl atts = new AttributesImpl();\r
+        String element = getElement();\r
+        handler.startElement(CATEGORY, element, element, atts);\r
+        if (content != null && content.length() > 0) {\r
+            char[] chars = content.toCharArray();\r
+            handler.characters(chars, 0, chars.length);\r
+        }\r
+        handler.endElement(CATEGORY, element, element);\r
+    }\r
+\r
+    /** @return type name */\r
+    public String getType() {\r
+        String className = getClass().getName();\r
+        return className.substring(className.lastIndexOf('.') + 3);\r
+    }\r
+    \r
+    /**\r
+     * @return a string representation of this object\r
+     * @see java.lang.Object#toString()\r
+     */\r
+    public String toString() {\r
+        return getType() + ": content=" + content;\r
+    }\r
+\r
+    /** @return element */\r
+    protected abstract String getElement();\r
+}\r
index a2aed74fff229d0079c153e5bfa5c25e2732fdc8..a90af2a9cdb6e4f7248f997853acb23136feb607 100644 (file)
@@ -41,6 +41,7 @@ public class PSExtensionElementMapping extends ElementMapping {
             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());
         }
     }
 
@@ -56,4 +57,9 @@ public class PSExtensionElementMapping extends ElementMapping {
         }
     }
 
+    static class PSSetPageDeviceMaker extends ElementMapping.Maker {
+        public FONode make(FONode parent) {
+            return new PSSetPageDeviceElement(parent);
+        }
+    }
 }
index 071ec1c255ab9b1cce5be7f62560ac2ee995b43f..6cc41f8ccece30b1f0d0e1448aadfaf902f2dc01 100644 (file)
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-/* $Id$ */
+/* $Id$ */
 
 package org.apache.fop.render.ps.extensions;
 
@@ -39,24 +39,24 @@ public class PSExtensionHandler extends DefaultHandler
     private StringBuffer content = new StringBuffer();
     private Attributes lastAttributes;
     
-    private PSSetupCode returnedObject;
+    private PSExtensionAttachment returnedObject;
     private ObjectBuiltListener listener;
     
     /** {@inheritDoc} */
     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 (localName.equals(PSSetupCode.ELEMENT)
+                    || localName.equals(PSSetPageDevice.ELEMENT)) {
                 //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 +68,13 @@ public class PSExtensionHandler extends DefaultHandler
 
     /** {@inheritDoc} */
     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());                
             }
         }    
         content.setLength(0); //Reset text buffer (see characters())
@@ -104,5 +107,4 @@ public class PSExtensionHandler extends DefaultHandler
     public void setObjectBuiltListener(ObjectBuiltListener listener) {
         this.listener = listener;
     }
-
 }
diff --git a/src/java/org/apache/fop/render/ps/extensions/PSSetPageDevice.java b/src/java/org/apache/fop/render/ps/extensions/PSSetPageDevice.java
new file mode 100644 (file)
index 0000000..5684ba6
--- /dev/null
@@ -0,0 +1,114 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/* $Id: $ */\r
+\r
+package org.apache.fop.render.ps.extensions;\r
+\r
+import org.xml.sax.ContentHandler;\r
+import org.xml.sax.SAXException;\r
+import org.xml.sax.helpers.AttributesImpl;\r
+\r
+/**\r
+ * Element for postscript setpagedevice instruction\r
+ * This is a an extension which provides a pass-through value\r
+ * dictionary object for the postscript setpagedevice instruction.\r
+ */\r
+public class PSSetPageDevice extends PSExtensionAttachment {\r
+    /** element name */\r
+    protected static final String ELEMENT = "ps-setpagedevice";\r
+\r
+    private static final String ATT_NAME = "name";\r
+\r
+    /**\r
+     * name attribute\r
+     */\r
+    protected String name = null;\r
+\r
+    /**\r
+     * default constructor\r
+     * @param content set page device dictionary\r
+     */\r
+    public PSSetPageDevice(String content) {\r
+        super(content);\r
+    }\r
+\r
+    /**\r
+     * constructor\r
+     * @param name name attribute of this setpagedevice content\r
+     * @param content set page device dictionary\r
+     */\r
+    public PSSetPageDevice(String name, String content) {\r
+        this(content);\r
+        this.name = name;\r
+    }\r
+\r
+    /**\r
+     * constructor\r
+     */\r
+    public PSSetPageDevice() {\r
+    }\r
+    \r
+    /** @return the name */\r
+    public String getName() {\r
+        return name;\r
+    }\r
+    \r
+    /**\r
+     * Sets the name of the setup code object.\r
+     * @param name The name to set.\r
+     */\r
+    public void setName(String name) {\r
+        this.name = name;\r
+    }\r
+\r
+    /**\r
+     * @return a string representation of this object \r
+     * @see java.lang.Object#toString()\r
+     */\r
+    public String toString() {\r
+        return "PSSetPageDevice(name=" + getName() + ", content='" + getContent() + "')";\r
+    }\r
+\r
+    /**\r
+     * @return a string representation of this object\r
+     * @see org.apache.fop.render.ps.extensions.PSExtensionAttachment#getElement()\r
+     */\r
+    protected String getElement() {\r
+        return ELEMENT;\r
+    }\r
+\r
+    /**\r
+     * Generates SAX events representing the object's state.\r
+     * @param handler ContentHandler instance to send the SAX events to\r
+     * @throws SAXException if there's a problem generating the SAX events\r
+     * @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler)\r
+     */\r
+    public void toSAX(ContentHandler handler) throws SAXException {\r
+        AttributesImpl atts = new AttributesImpl();\r
+        if (name != null && name.length() > 0) {\r
+            atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", name);\r
+        }\r
+        String element = getElement();\r
+        handler.startElement(CATEGORY, element, element, atts);\r
+        if (content != null && content.length() > 0) {\r
+            char[] chars = content.toCharArray();\r
+            handler.characters(chars, 0, chars.length);\r
+        }\r
+        handler.endElement(CATEGORY, element, element);\r
+    }\r
+}\r
diff --git a/src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java b/src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java
new file mode 100644 (file)
index 0000000..da51e95
--- /dev/null
@@ -0,0 +1,93 @@
+/*\r
+ * Licensed to the Apache Software Foundation (ASF) under one or more\r
+ * contributor license agreements.  See the NOTICE file distributed with\r
+ * this work for additional information regarding copyright ownership.\r
+ * The ASF licenses this file to You under the Apache License, Version 2.0\r
+ * (the "License"); you may not use this file except in compliance with\r
+ * the License.  You may obtain a copy of the License at\r
+ *\r
+ *      http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+/* $Id: $ */\r
+\r
+package org.apache.fop.render.ps.extensions;\r
+\r
+import org.apache.fop.apps.FOPException;\r
+import org.apache.fop.fo.Constants;\r
+import org.apache.fop.fo.FONode;\r
+import org.apache.fop.fo.PropertyList;\r
+import org.apache.fop.fo.ValidationException;\r
+import org.apache.fop.fo.extensions.ExtensionAttachment;\r
+import org.xml.sax.Attributes;\r
+import org.xml.sax.Locator;\r
+\r
+/**\r
+ * Extension element for ps:ps-setpagedevice. \r
+ */\r
+public class PSSetPageDeviceElement extends AbstractPSExtensionElement {\r
+\r
+    /**\r
+     * Main constructor\r
+     * @param parent parent FO node\r
+     */\r
+    protected PSSetPageDeviceElement(FONode parent) {\r
+        super(parent);\r
+    }\r
+\r
+    /**\r
+     * Called after processNode() is called. Subclasses can do additional processing.\r
+     * @throws FOPException if there's a problem during processing\r
+     * @see org.apache.fop.fo.FONode#startOfNode()\r
+     */\r
+    protected void startOfNode() throws FOPException {\r
+        super.startOfNode();\r
+        if ( !((parent.getNameId() == Constants.FO_DECLARATIONS)\r
+                || (parent.getNameId() == Constants.FO_SIMPLE_PAGE_MASTER)) ) {\r
+            throw new ValidationException( getName()\r
+                    + " must be a child of fo:declarations or fo:simple-page-master.");\r
+        }\r
+    }\r
+\r
+    /**\r
+     * Initialize the node with its name, location information, and attributes\r
+     * The attributes must be used immediately as the sax attributes\r
+     * will be altered for the next element.\r
+     * @param elementName element name (e.g., "fo:block")\r
+     * @param locator Locator object (ignored by default)\r
+     * @param attlist Collection of attributes passed to us from the parser.\r
+     * @param propertyList property list\r
+     * @throws FOPException if there's a problem during processing\r
+     * @see org.apache.fop.fo.FONode#processNode\r
+     */\r
+    public void processNode(String elementName, Locator locator, \r
+                            Attributes attlist, PropertyList propertyList)\r
+                                throws FOPException {\r
+        String name = attlist.getValue("name");\r
+        if (name != null && name.length() > 0) {\r
+            ((PSSetPageDevice)getExtensionAttachment()).setName(name);\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @return local name \r
+     * @see org.apache.fop.fo.FONode#getLocalName() */\r
+    public String getLocalName() {\r
+        return "ps-setpagedevice";\r
+    }\r
+\r
+    /**\r
+     * @return a new PSSetPageDevice object\r
+     * @see org.apache.fop.render.ps.extensions.AbstractPSExtensionElement\r
+     * #instantiateExtensionAttachment()\r
+     */\r
+    protected ExtensionAttachment instantiateExtensionAttachment() {\r
+        return new PSSetPageDevice();\r
+    }\r
+}\r
index 7ad66427ce1abee58c8177278f493496701f5558..eb3ed0e399dca3bce1ddf46deb6ac8909bdaf3bc 100644 (file)
 
 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 @@ import org.xml.sax.helpers.AttributesImpl;
 /**
  * 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 @@ public class PSSetupCode implements ExtensionAttachment, Serializable, XMLizable
      * @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;
@@ -88,11 +75,16 @@ public class PSSetupCode implements ExtensionAttachment, Serializable, XMLizable
     
     /** {@inheritDoc} */
     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";
+    /**
+     * @return the element name
+     * @see org.apache.fop.render.ps.extensions.PSExtensionAttachment#getElement()
+     */
+    protected String getElement() {
+        return ELEMENT;
+    }
     
     /** {@inheritDoc} */
     public void toSAX(ContentHandler handler) throws SAXException {
@@ -100,12 +92,12 @@ public class PSSetupCode implements ExtensionAttachment, Serializable, XMLizable
         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);
     }
-    
 }
diff --git a/test/layoutengine/standard-testcases/ps-extension_2.xml b/test/layoutengine/standard-testcases/ps-extension_2.xml
new file mode 100644 (file)
index 0000000..d0c18a9
--- /dev/null
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="UTF-8"?>\r
+<!--\r
+  Licensed to the Apache Software Foundation (ASF) under one or more\r
+  contributor license agreements.  See the NOTICE file distributed with\r
+  this work for additional information regarding copyright ownership.\r
+  The ASF licenses this file to You under the Apache License, Version 2.0\r
+  (the "License"); you may not use this file except in compliance with\r
+  the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+  Unless required by applicable law or agreed to in writing, software\r
+  distributed under the License is distributed on an "AS IS" BASIS,\r
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+  See the License for the specific language governing permissions and\r
+  limitations under the License.\r
+-->\r
+<!-- $Id: $ -->\r
+<testcase>\r
+  <info>\r
+    <p>\r
+      This test checks the PostScript extension for custom setpagedevice calls and comments.\r
+      The extension attachments need to show up in the area tree XML so the AreaTreeParser can fully restore the area tree.\r
+    </p>\r
+  </info>\r
+  <fo>\r
+    <fo:root xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:ps="http://xmlgraphics.apache.org/fop/postscript">\r
+      <fo:layout-master-set>\r
+        <fo:simple-page-master master-name="A4" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm">\r
+          <ps:ps-setpagedevice name="lower tray"><![CDATA[ << /MediaPosition /4 >> ]]></ps:ps-setpagedevice>\r
+          <fo:region-body/>\r
+        </fo:simple-page-master>\r
+        <fo:simple-page-master master-name="A4a" page-height="29.7cm" page-width="21cm" margin-top="1cm" margin-bottom="2cm" margin-left="2.5cm" margin-right="2.5cm">\r
+          <ps:ps-setpagedevice name="upper tray"><![CDATA[ << /MediaPosition /1 >> ]]></ps:ps-setpagedevice>\r
+          <fo:region-body background-color="orange"/>\r
+        </fo:simple-page-master>\r
+        <fo:page-sequence-master master-name="complex">\r
+         <fo:repeatable-page-master-reference master-reference="A4" maximum-repeats="1"/>\r
+         <fo:repeatable-page-master-reference master-reference="A4a" maximum-repeats="1"/>\r
+         <fo:repeatable-page-master-reference master-reference="A4"/>\r
+        </fo:page-sequence-master>\r
+      </fo:layout-master-set>\r
+      <fo:declarations>\r
+        <ps:ps-setpagedevice name="autofeed"><![CDATA[ << /ManualFeed false >> ]]></ps:ps-setpagedevice>\r
+      </fo:declarations>\r
+      <fo:page-sequence master-reference="complex">\r
+        <fo:flow flow-name="xsl-region-body">\r
+          <fo:block>Hello World!</fo:block>\r
+          <fo:block break-before="page"/>\r
+          <fo:block>Hello World!</fo:block>\r
+          <fo:block break-before="page"/>\r
+          <fo:block>Hello World!</fo:block>\r
+        </fo:flow>\r
+      </fo:page-sequence>\r
+  </fo:root>\r
+  </fo>\r
+  <checks>\r
+    <eval expected="1" xpath="count(/areaTree/child::extension-attachments/child::*)"/>\r
+    <eval expected="autofeed"       xpath="/areaTree/extension-attachments/child::*[1]/@name"/>\r
+\r
+    <eval expected="1" xpath="count(/areaTree/pageSequence/pageViewport[@simple-page-master-name='A4' and @nr=1]/page/extension-attachments/child::*)"/>\r
+    <eval expected="lower tray" xpath="/areaTree/pageSequence/pageViewport[@simple-page-master-name='A4' and @nr=1]/page/extension-attachments/child::*[1]/@name"/>\r
+\r
+    <eval expected="1" xpath="count(/areaTree/pageSequence/pageViewport[@simple-page-master-name='A4a' and @nr=2]/page/extension-attachments/child::*)"/>\r
+    <eval expected="upper tray" xpath="/areaTree/pageSequence/pageViewport[@simple-page-master-name='A4a' and @nr=2]/page/extension-attachments/child::*[1]/@name"/>\r
+\r
+    <eval expected="1" xpath="count(/areaTree/pageSequence/pageViewport[@simple-page-master-name='A4' and @nr=3]/page/extension-attachments/child::*)"/>\r
+    <eval expected="lower tray" xpath="/areaTree/pageSequence/pageViewport[@simple-page-master-name='A4' and @nr=3]/page/extension-attachments/child::*[1]/@name"/>\r
+  </checks>\r
+</testcase>\r