<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
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>
</body>
</document>
+
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();
+ }
}
--- /dev/null
+/*\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
--- /dev/null
+/*\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
--- /dev/null
+/*\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
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;
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;
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}
*/
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()});
IOUtils.closeQuietly(gen.getOutputStream());
rewritePostScriptFile();
}
+ this.pageDeviceDictionary.clear();
}
/**
}
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);
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);
}
/**
* {@inheritDoc}
- * int, int, int, int, java.util.Map)
*/
protected RendererContext createRendererContext(int x, int y, int width, int height,
Map foreignAttributes) {
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;
+ }
}
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));
}
}
}
--- /dev/null
+/*\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
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());
}
}
}
}
+ static class PSSetPageDeviceMaker extends ElementMapping.Maker {
+ public FONode make(FONode parent) {
+ return new PSSetPageDeviceElement(parent);
+ }
+ }
}
* limitations under the License.
*/
-/* $Id$ */
+/* $Id: $ */
package org.apache.fop.render.ps.extensions;
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 {
/** {@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())
public void setObjectBuiltListener(ObjectBuiltListener listener) {
this.listener = listener;
}
-
}
--- /dev/null
+/*\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
--- /dev/null
+/*\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
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;
/**
* 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
}
/**
* @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;
/** {@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 {
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);
}
-
}
--- /dev/null
+<?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