diff options
Diffstat (limited to 'src')
13 files changed, 1052 insertions, 110 deletions
diff --git a/src/documentation/content/xdocs/trunk/output.xml b/src/documentation/content/xdocs/trunk/output.xml index 21743519f..798c61811 100644 --- a/src/documentation/content/xdocs/trunk/output.xml +++ b/src/documentation/content/xdocs/trunk/output.xml @@ -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> + diff --git a/src/java/org/apache/fop/area/PageViewport.java b/src/java/org/apache/fop/area/PageViewport.java index 184eb9d32..af557ade9 100644 --- a/src/java/org/apache/fop/area/PageViewport.java +++ b/src/java/org/apache/fop/area/PageViewport.java @@ -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 index 000000000..dbf7173a5 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSDictionary.java @@ -0,0 +1,312 @@ +/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id: $ */
+
+package org.apache.fop.render.ps;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+
+/**
+ * This class is used to encapsulate postscript dictionary objects.
+ */
+public class PSDictionary extends java.util.HashMap {
+
+ private static final long serialVersionUID = 815367222496219197L;
+
+ /**
+ * This class is used to parse dictionary strings.
+ */
+ private static class Maker {
+
+ /**
+ * Simple token holding class
+ */
+ private class Token {
+ /**
+ * start index in string
+ */
+ private int startIndex = -1;
+
+ /**
+ * end index in string
+ */
+ private int endIndex = -1;
+
+ /**
+ * token string value
+ */
+ private String value;
+ }
+
+ private static final String[][] BRACES = {
+ {"<<", ">>"},
+ {"[", "]"},
+ {"{", "}"}
+ };
+
+ private static final int OPENING = 0;
+ private static final int CLOSING = 1;
+ private static final int DICTIONARY = 0;
+ private static final int ARRAY = 1;
+ private static final int PROCEDURE = 2;
+
+ /**
+ * Returns a Token containing the start, end index and value of the next token
+ * found in a given string
+ *
+ * @param str
+ * string to search
+ * @param fromIndex
+ * search from index
+ * @return Token containing the start, end index and value of the next token
+ */
+ protected Token nextToken(String str, int fromIndex) {
+ Token t = null;
+ for (int i = fromIndex; i < str.length(); i++) {
+ boolean isWhitespace = Character.isWhitespace(str.charAt(i));
+ // start index found
+ if (t == null && !isWhitespace) {
+ t = new Token();
+ t.startIndex = i;
+ // end index found
+ } else if (t != null && isWhitespace) {
+ t.endIndex = i;
+ break;
+ }
+ }
+ // start index found
+ if (t != null) {
+ // end index not found so take end of string
+ if (t.endIndex == -1) {
+ t.endIndex = str.length();
+ }
+ t.value = str.substring(t.startIndex, t.endIndex);
+ }
+ return t;
+ }
+
+ /**
+ * Returns the closing brace index from a given string searches from a
+ * given index
+ *
+ * @param str
+ * string to search
+ * @param braces
+ * string array of opening and closing brace
+ * @param fromIndex
+ * searches from index
+ * @return matching brace index
+ * @throws org.apache.fop.render.ps.PSDictionaryFormatException
+ * thrown in the event that a parsing error occurred
+ */
+ private int indexOfMatchingBrace(String str, String[] braces,
+ int fromIndex) throws PSDictionaryFormatException {
+ final int len = str.length();
+ if (braces.length != 2) {
+ throw new PSDictionaryFormatException("Wrong number of braces");
+ }
+ for (int openCnt = 0, closeCnt = 0; fromIndex < len; fromIndex++) {
+ if (str.startsWith(braces[OPENING], fromIndex)) {
+ openCnt++;
+ } else if (str.startsWith(braces[CLOSING], fromIndex)) {
+ closeCnt++;
+ if (openCnt > 0 && openCnt == closeCnt) {
+ return fromIndex; // found
+ }
+ }
+ }
+ return -1; // not found
+ }
+
+ /**
+ * Strips braces from complex object string
+ *
+ * @param str
+ * String to parse
+ * @param braces
+ * String array containing opening and closing braces
+ * @return String with braces stripped
+ * @throws
+ * org.apache.fop.render.ps.PSDictionaryFormatException object format exception
+ */
+ private String stripBraces(String str, String[] braces) throws PSDictionaryFormatException {
+ // find first opening brace
+ int firstIndex = str.indexOf(braces[OPENING]);
+ if (firstIndex == -1) {
+ throw new PSDictionaryFormatException(
+ "Failed to find opening parameter '" + braces[OPENING]
+ + "");
+ }
+
+ // find last matching brace
+ int lastIndex = indexOfMatchingBrace(str, braces, firstIndex);
+ if (lastIndex == -1) {
+ throw new PSDictionaryFormatException(
+ "Failed to find matching closing parameter '"
+ + braces[CLOSING] + "'");
+ }
+
+ // strip brace and trim
+ int braceLen = braces[OPENING].length();
+ str = str.substring(firstIndex + braceLen, lastIndex).trim();
+ return str;
+ }
+
+ /**
+ * Parses a dictionary string and provides a dictionary object
+ *
+ * @param str a dictionary string
+ * @return A postscript dictionary object
+ * @throws
+ * PSDictionaryFormatException thrown if a dictionary format exception occurs
+ */
+ public PSDictionary parseDictionary(String str) throws PSDictionaryFormatException {
+ PSDictionary dictionary = new PSDictionary();
+ str = stripBraces(str.trim(), BRACES[DICTIONARY]);
+ // length of dictionary string
+ final int len = str.length();
+
+ Token keyToken;
+ for (int currIndex = 0; (keyToken = nextToken(str, currIndex)) != null
+ && currIndex <= len;) {
+ if (keyToken.value == null) {
+ throw new PSDictionaryFormatException("Failed to parse object key");
+ }
+ Token valueToken = nextToken(str, keyToken.endIndex + 1);
+ String[] braces = null;
+ for (int i = 0; i < BRACES.length; i++) {
+ if (valueToken.value.startsWith(BRACES[i][OPENING])) {
+ braces = BRACES[i];
+ break;
+ }
+ }
+ Object obj = null;
+ if (braces != null) {
+ // find closing brace
+ valueToken.endIndex = indexOfMatchingBrace(str, braces,
+ valueToken.startIndex)
+ + braces[OPENING].length();
+ if (valueToken.endIndex < 0) {
+ throw new PSDictionaryFormatException("Closing value brace '"
+ + braces[CLOSING] + "' not found for key '"
+ + keyToken.value + "'");
+ }
+ valueToken.value = str.substring(valueToken.startIndex, valueToken.endIndex);
+ }
+ if (braces == null || braces == BRACES[PROCEDURE]) {
+ obj = valueToken.value;
+ } else if (BRACES[ARRAY] == braces) {
+ List objList = new java.util.ArrayList();
+ String objString = stripBraces(valueToken.value, braces);
+ StringTokenizer tokenizer = new StringTokenizer(objString, ",");
+ while (tokenizer.hasMoreTokens()) {
+ objList.add(tokenizer.nextToken());
+ }
+ obj = objList;
+ } else if (BRACES[DICTIONARY] == braces) {
+ obj = parseDictionary(valueToken.value);
+ }
+ dictionary.put(keyToken.value, obj);
+ currIndex = valueToken.endIndex + 1;
+ }
+ return dictionary;
+ }
+ }
+
+ /**
+ * Parses a given a dictionary string and returns an object
+ *
+ * @param str dictionary string
+ * @return dictionary object
+ * @throws PSDictionaryFormatException object format exception
+ */
+ public static PSDictionary valueOf(String str) throws PSDictionaryFormatException {
+ return (new Maker()).parseDictionary(str);
+ }
+
+ /**
+ * @param obj object to test equality against
+ * @return whether a given object is equal to this dictionary object
+ * @see java.lang.Object#equals(Object)
+ */
+ public boolean equals(Object obj) {
+ if (!(obj instanceof PSPageDeviceDictionary)) {
+ return false;
+ }
+ PSDictionary dictionaryObj = (PSDictionary) obj;
+ if (dictionaryObj.size() != size()) {
+ return false;
+ }
+ for (Iterator it = keySet().iterator(); it.hasNext();) {
+ String key = (String) it.next();
+ if (!dictionaryObj.containsKey(key)) {
+ return false;
+ }
+ if (!dictionaryObj.get(key).equals(get(key))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * @return a hash code value for this object.
+ * @see java.lang.Object#hashCode()
+ */
+ public int hashCode() {
+ int hashCode = 7;
+ for (Iterator it = values().iterator(); it.hasNext();) {
+ Object value = it.next();
+ hashCode += value.hashCode();
+ }
+ return hashCode;
+ }
+
+ /**
+ * @return a string representation of this dictionary
+ * @see java.lang.String#toString()
+ */
+ public String toString() {
+ if (isEmpty()) {
+ return "";
+ }
+ StringBuffer sb = new StringBuffer("<<\n");
+ for (Iterator it = super.keySet().iterator(); it.hasNext();) {
+ String key = (String) it.next();
+ sb.append(" " + key + " ");
+ Object obj = super.get(key);
+ if (obj instanceof java.util.ArrayList) {
+ List array = (List)obj;
+ String str = "[";
+ for (int i = 0; i < array.size(); i++) {
+ Object element = array.get(i);
+ str += element + " ";
+ }
+ str = str.trim();
+ str += "]";
+ sb.append(str + "\n");
+ } else {
+ sb.append(obj.toString() + "\n");
+ }
+ }
+ sb.append(">>");
+ return sb.toString();
+ }
+}
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 index 000000000..2153e8116 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSDictionaryFormatException.java @@ -0,0 +1,37 @@ +/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id: $ */
+
+package org.apache.fop.render.ps;
+
+/**
+ * Thrown to indicate that a formatting error has occured when
+ * trying to parse a postscript dictionary object
+ */
+public class PSDictionaryFormatException extends Exception {
+
+ private static final long serialVersionUID = 6492321557297860931L;
+
+ /**
+ * Default constructor
+ * @param string error message
+ */
+ public PSDictionaryFormatException(String string) {
+ super(string);
+ }
+}
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 index 000000000..c327423ef --- /dev/null +++ b/src/java/org/apache/fop/render/ps/PSPageDeviceDictionary.java @@ -0,0 +1,110 @@ +/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id: $ */
+
+package org.apache.fop.render.ps;
+
+/**
+ * Postscript page device dictionary object
+ *
+ * This object is used by the postscript renderer to hold postscript page device
+ * values. It can also be used to minimize the number of setpagedevice calls when
+ * DSC compliance is false.
+ */
+public class PSPageDeviceDictionary extends PSDictionary {
+
+ private static final long serialVersionUID = 845943256485806509L;
+
+ /**
+ * Whether or not the contents of the dictionary are flushed on retrieval
+ */
+ private boolean flushOnRetrieval = false;
+
+ /**
+ * Dictionary content that has not been output/written yet
+ */
+ private PSDictionary unRetrievedContentDictionary;
+
+ /**
+ * @param key key with which the specified value is to be associated.
+ * @param value value to be associated with the specified key.
+ * @return the previous value associated with the key or null
+ * @see java.util.Map#put(Object, Object)
+ */
+ public Object put(Object key, Object value) {
+ Object previousValue = super.put(key, value);
+ if (flushOnRetrieval) {
+ if (previousValue == null || !previousValue.equals(value)) {
+ unRetrievedContentDictionary.put(key, value);
+ }
+ }
+ return previousValue;
+ }
+
+ /**
+ * @see java.util.Map#clear()
+ */
+ public void clear() {
+ super.clear();
+ if (unRetrievedContentDictionary != null) {
+ unRetrievedContentDictionary.clear();
+ }
+ }
+
+ /**
+ * Returns <tt>true</tt> if this map contains no key-value mappings.
+ *
+ * @return <tt>true</tt> if this map contains no key-value mappings.
+ */
+ public boolean isEmpty() {
+ if (flushOnRetrieval) {
+ return unRetrievedContentDictionary.isEmpty();
+ }
+ return super.isEmpty();
+ }
+
+ /**
+ * The contents of the dictionary are flushed when written
+ * @param flushOnRetrieval boolean value
+ */
+ public void setFlushOnRetrieval(boolean flushOnRetrieval) {
+ this.flushOnRetrieval = flushOnRetrieval;
+ if (flushOnRetrieval) {
+ unRetrievedContentDictionary = new PSDictionary();
+ }
+ }
+
+ /**
+ * Returns a dictionary string with containing all unwritten content note:
+ * unnecessary writes are important as there is a device specific
+ * initgraphics call by the underlying postscript interpreter on every
+ * setpagedevice call which can result in blank pages etc.
+ *
+ * @return unwritten content dictionary string
+ */
+ public String getContent() {
+ String content;
+ if (flushOnRetrieval) {
+ content = unRetrievedContentDictionary.toString();
+ unRetrievedContentDictionary.clear();
+ } else {
+ content = super.toString();
+ }
+ return content;
+ }
+}
diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index bb77dd55a..5d823e86e 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -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; + } } diff --git a/src/java/org/apache/fop/render/ps/PSRendererConfigurator.java b/src/java/org/apache/fop/render/ps/PSRendererConfigurator.java index c7b5a025b..6caa92c85 100644 --- a/src/java/org/apache/fop/render/ps/PSRendererConfigurator.java +++ b/src/java/org/apache/fop/render/ps/PSRendererConfigurator.java @@ -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 index 000000000..0fb623bdc --- /dev/null +++ b/src/java/org/apache/fop/render/ps/extensions/PSExtensionAttachment.java @@ -0,0 +1,108 @@ +/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id: $ */
+
+package org.apache.fop.render.ps.extensions;
+
+import org.apache.fop.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 abstract class PSExtensionAttachment implements ExtensionAttachment, XMLizable {
+
+ /** extension node content */
+ protected String content;
+
+ /** The category URI for this extension attachment. */
+ public static final String CATEGORY = "apache:fop:extensions:postscript";
+
+ /**
+ * Default constructor.
+ * @param content the content of the setup code object
+ */
+ public PSExtensionAttachment(String content) {
+ this.content = content;
+ }
+
+ /**
+ * No-argument contructor.
+ */
+ public PSExtensionAttachment() {
+ }
+
+ /**
+ * @return the category URI
+ * @see org.apache.fop.fo.extensions.ExtensionAttachment#getCategory()
+ */
+ public String getCategory() {
+ return CATEGORY;
+ }
+
+ /** @return the content */
+ public String getContent() {
+ return content;
+ }
+
+ /**
+ * Sets the content for the setup code object.
+ * @param content The content to set.
+ */
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ /**
+ * Generates SAX events representing the object's state.
+ *
+ * @param handler ContentHandler instance to send the SAX events to
+ * @throws SAXException if there's a problem generating the SAX events
+ * @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler)
+ */
+ public void toSAX(ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ String element = getElement();
+ handler.startElement(CATEGORY, element, element, atts);
+ if (content != null && content.length() > 0) {
+ char[] chars = content.toCharArray();
+ handler.characters(chars, 0, chars.length);
+ }
+ handler.endElement(CATEGORY, element, element);
+ }
+
+ /** @return type name */
+ public String getType() {
+ String className = getClass().getName();
+ return className.substring(className.lastIndexOf('.') + 3);
+ }
+
+ /**
+ * @return a string representation of this object
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return getType() + ": content=" + content;
+ }
+
+ /** @return element */
+ protected abstract String getElement();
+}
diff --git a/src/java/org/apache/fop/render/ps/extensions/PSExtensionElementMapping.java b/src/java/org/apache/fop/render/ps/extensions/PSExtensionElementMapping.java index a2aed74ff..a90af2a9c 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSExtensionElementMapping.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSExtensionElementMapping.java @@ -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); + } + } } diff --git a/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java b/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java index 071ec1c25..6cc41f8cc 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSExtensionHandler.java @@ -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 index 000000000..5684ba6a3 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/extensions/PSSetPageDevice.java @@ -0,0 +1,114 @@ +/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id: $ */
+
+package org.apache.fop.render.ps.extensions;
+
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+/**
+ * Element for postscript setpagedevice instruction
+ * This is a an extension which provides a pass-through value
+ * dictionary object for the postscript setpagedevice instruction.
+ */
+public class PSSetPageDevice extends PSExtensionAttachment {
+ /** element name */
+ protected static final String ELEMENT = "ps-setpagedevice";
+
+ private static final String ATT_NAME = "name";
+
+ /**
+ * name attribute
+ */
+ protected String name = null;
+
+ /**
+ * default constructor
+ * @param content set page device dictionary
+ */
+ public PSSetPageDevice(String content) {
+ super(content);
+ }
+
+ /**
+ * constructor
+ * @param name name attribute of this setpagedevice content
+ * @param content set page device dictionary
+ */
+ public PSSetPageDevice(String name, String content) {
+ this(content);
+ this.name = name;
+ }
+
+ /**
+ * constructor
+ */
+ public PSSetPageDevice() {
+ }
+
+ /** @return the name */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of the setup code object.
+ * @param name The name to set.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * @return a string representation of this object
+ * @see java.lang.Object#toString()
+ */
+ public String toString() {
+ return "PSSetPageDevice(name=" + getName() + ", content='" + getContent() + "')";
+ }
+
+ /**
+ * @return a string representation of this object
+ * @see org.apache.fop.render.ps.extensions.PSExtensionAttachment#getElement()
+ */
+ protected String getElement() {
+ return ELEMENT;
+ }
+
+ /**
+ * Generates SAX events representing the object's state.
+ * @param handler ContentHandler instance to send the SAX events to
+ * @throws SAXException if there's a problem generating the SAX events
+ * @see org.apache.fop.util.XMLizable#toSAX(org.xml.sax.ContentHandler)
+ */
+ public void toSAX(ContentHandler handler) throws SAXException {
+ AttributesImpl atts = new AttributesImpl();
+ if (name != null && name.length() > 0) {
+ atts.addAttribute(null, ATT_NAME, ATT_NAME, "CDATA", name);
+ }
+ String element = getElement();
+ handler.startElement(CATEGORY, element, element, atts);
+ if (content != null && content.length() > 0) {
+ char[] chars = content.toCharArray();
+ handler.characters(chars, 0, chars.length);
+ }
+ handler.endElement(CATEGORY, element, element);
+ }
+}
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 index 000000000..da51e9531 --- /dev/null +++ b/src/java/org/apache/fop/render/ps/extensions/PSSetPageDeviceElement.java @@ -0,0 +1,93 @@ +/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id: $ */
+
+package org.apache.fop.render.ps.extensions;
+
+import org.apache.fop.apps.FOPException;
+import org.apache.fop.fo.Constants;
+import org.apache.fop.fo.FONode;
+import org.apache.fop.fo.PropertyList;
+import org.apache.fop.fo.ValidationException;
+import org.apache.fop.fo.extensions.ExtensionAttachment;
+import org.xml.sax.Attributes;
+import org.xml.sax.Locator;
+
+/**
+ * Extension element for ps:ps-setpagedevice.
+ */
+public class PSSetPageDeviceElement extends AbstractPSExtensionElement {
+
+ /**
+ * Main constructor
+ * @param parent parent FO node
+ */
+ protected PSSetPageDeviceElement(FONode parent) {
+ super(parent);
+ }
+
+ /**
+ * Called after processNode() is called. Subclasses can do additional processing.
+ * @throws FOPException if there's a problem during processing
+ * @see org.apache.fop.fo.FONode#startOfNode()
+ */
+ protected void startOfNode() throws FOPException {
+ super.startOfNode();
+ if ( !((parent.getNameId() == Constants.FO_DECLARATIONS)
+ || (parent.getNameId() == Constants.FO_SIMPLE_PAGE_MASTER)) ) {
+ throw new ValidationException( getName()
+ + " must be a child of fo:declarations or fo:simple-page-master.");
+ }
+ }
+
+ /**
+ * Initialize the node with its name, location information, and attributes
+ * The attributes must be used immediately as the sax attributes
+ * will be altered for the next element.
+ * @param elementName element name (e.g., "fo:block")
+ * @param locator Locator object (ignored by default)
+ * @param attlist Collection of attributes passed to us from the parser.
+ * @param propertyList property list
+ * @throws FOPException if there's a problem during processing
+ * @see org.apache.fop.fo.FONode#processNode
+ */
+ public void processNode(String elementName, Locator locator,
+ Attributes attlist, PropertyList propertyList)
+ throws FOPException {
+ String name = attlist.getValue("name");
+ if (name != null && name.length() > 0) {
+ ((PSSetPageDevice)getExtensionAttachment()).setName(name);
+ }
+ }
+
+ /**
+ * @return local name
+ * @see org.apache.fop.fo.FONode#getLocalName() */
+ public String getLocalName() {
+ return "ps-setpagedevice";
+ }
+
+ /**
+ * @return a new PSSetPageDevice object
+ * @see org.apache.fop.render.ps.extensions.AbstractPSExtensionElement
+ * #instantiateExtensionAttachment()
+ */
+ protected ExtensionAttachment instantiateExtensionAttachment() {
+ return new PSSetPageDevice();
+ }
+}
diff --git a/src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java b/src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java index 7ad66427c..eb3ed0e39 100644 --- a/src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java +++ b/src/java/org/apache/fop/render/ps/extensions/PSSetupCode.java @@ -19,10 +19,6 @@ package org.apache.fop.render.ps.extensions; -import java.io.Serializable; - -import org.apache.fop.fo.extensions.ExtensionAttachment; -import org.apache.fop.util.XMLizable; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @@ -30,19 +26,23 @@ 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); } - } |