diff options
author | Jeremias Maerki <jeremias@apache.org> | 2009-02-19 18:04:18 +0000 |
---|---|---|
committer | Jeremias Maerki <jeremias@apache.org> | 2009-02-19 18:04:18 +0000 |
commit | 223eb5df1f7597ac5269eb8dce45b6a7450144b8 (patch) | |
tree | b6475383dd7fca2a53402b19d22b682276d8d6d1 /src | |
parent | 51c210eea6bf627053854359388c1ad14203c0aa (diff) | |
download | xmlgraphics-fop-223eb5df1f7597ac5269eb8dce45b6a7450144b8.tar.gz xmlgraphics-fop-223eb5df1f7597ac5269eb8dce45b6a7450144b8.zip |
Bugzilla #46705:
Accessibility and Tagged PDF Support
Submitted by: Jost Klopfstein <jost.klopfstein.at.gmail.com>
Changes to patch by jeremias:
- Some style fixes
- Various simplifications
- Removal of dead code
- Addressed some issues raised in Bugzilla (work in progress)
- Fixed a couple of bugs on leader handling detected while testing (an NPE remains in leader_leader-pattern_use-content.xml)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/branches/Temp_Accessibility@745949 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src')
72 files changed, 2292 insertions, 112 deletions
diff --git a/src/java/META-INF/services/org.apache.fop.fo.ElementMapping b/src/java/META-INF/services/org.apache.fop.fo.ElementMapping index 0194b19db..09c79f5b7 100644 --- a/src/java/META-INF/services/org.apache.fop.fo.ElementMapping +++ b/src/java/META-INF/services/org.apache.fop.fo.ElementMapping @@ -1,10 +1,11 @@ -org.apache.fop.fo.FOElementMapping -org.apache.fop.fo.extensions.svg.SVGElementMapping -org.apache.fop.fo.extensions.svg.BatikExtensionElementMapping -org.apache.fop.fo.extensions.ExtensionElementMapping -org.apache.fop.fo.extensions.OldExtensionElementMapping -org.apache.fop.fo.extensions.xmp.XMPElementMapping -org.apache.fop.fo.extensions.xmp.RDFElementMapping -org.apache.fop.render.ps.extensions.PSExtensionElementMapping -org.apache.fop.render.afp.extensions.AFPElementMapping +org.apache.fop.fo.FOElementMapping
+org.apache.fop.fo.extensions.svg.SVGElementMapping
+org.apache.fop.fo.extensions.svg.BatikExtensionElementMapping
+org.apache.fop.fo.extensions.ExtensionElementMapping
+org.apache.fop.fo.extensions.InternalElementMapping
+org.apache.fop.fo.extensions.OldExtensionElementMapping
+org.apache.fop.fo.extensions.xmp.XMPElementMapping
+org.apache.fop.fo.extensions.xmp.RDFElementMapping
+org.apache.fop.render.ps.extensions.PSExtensionElementMapping
+org.apache.fop.render.afp.extensions.AFPElementMapping
org.apache.fop.render.pcl.extensions.PCLElementMapping
\ No newline at end of file diff --git a/src/java/org/apache/fop/accessibility/AccessibilityUtil.java b/src/java/org/apache/fop/accessibility/AccessibilityUtil.java new file mode 100644 index 000000000..0dc5840b6 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/AccessibilityUtil.java @@ -0,0 +1,89 @@ +/* + * 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.accessibility; + +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.stream.StreamSource; + +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; + +/** + * Utility class for FOP's accessibility features. It provides the stylesheets used for processing + * the incoming XSL-FO stream and for setting up the transformation. + */ +public class AccessibilityUtil { + + private static SAXTransformerFactory tfactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + private static Templates addPtrTemplates; + private static Templates reduceFOTemplates; + + public static DefaultHandler decorateDefaultHandler(DefaultHandler handler, + FOUserAgent userAgent) throws FOPException { + DefaultHandler transformNode = new TransformerNodeEndProcessing( + getAddPtrTemplates(), handler, userAgent); + return transformNode; + } + + /** + * Returns the addPtr.xsl stylesheet. + * @return the addPtr.xsl stylesheet + * @throws FOPException if transform fails + */ + public static synchronized Templates getAddPtrTemplates() throws FOPException { + if (addPtrTemplates == null) { + //Load and cache stylesheet + Source src = new StreamSource( + AccessibilityUtil.class.getResource("addPtr.xsl").toExternalForm()); + try { + addPtrTemplates = tfactory.newTemplates(src); + } catch (TransformerConfigurationException e) { + throw new FOPException(e); + } + } + return addPtrTemplates; + } + + /** + * Returns the reduceFOTree.xsl stylesheet + * @return the reduceFOTree.xsl stylesheet + * @throws FOPException if an error occurs loading the stylesheet + */ + public static synchronized Templates getReduceFOTreeTemplates() throws FOPException { + if (reduceFOTemplates == null) { + //Load and cache stylesheet + Source src = new StreamSource( + AccessibilityUtil.class.getResource("reduceFOTree.xsl").toExternalForm()); + try { + reduceFOTemplates = tfactory.newTemplates(src); + } catch (TransformerConfigurationException e) { + throw new FOPException(e); + } + } + return reduceFOTemplates; + } + }
\ No newline at end of file diff --git a/src/java/org/apache/fop/accessibility/TransformerNode.java b/src/java/org/apache/fop/accessibility/TransformerNode.java new file mode 100644 index 000000000..115c13703 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/TransformerNode.java @@ -0,0 +1,321 @@ +/* + * 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.accessibility; + +import java.io.File; + +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXResult; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamSource; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.fop.apps.FOPException; + +/** + * Used for accessibility to run required xslt transforms + */ +class TransformerNode extends DefaultHandler { + + private TransformerHandler transformerHandler; + + /** + * happens after setParams have been broadcast. + * + * @param downstreamHandler + * the handler passed in + * @param xsltFile + * for transform + * @throws FOPException + * for general errors + */ + public TransformerNode(DefaultHandler downstreamHandler, File xsltFile) throws FOPException { + try { + TransformerFactory transFact = TransformerFactory.newInstance(); + SAXTransformerFactory saxTFactory = ((SAXTransformerFactory)transFact); + StreamSource ss = new StreamSource(xsltFile); + transformerHandler = saxTFactory.newTransformerHandler(ss); + SAXResult saxResult = new SAXResult(); + saxResult.setHandler(downstreamHandler); + transformerHandler.setResult(saxResult); + } catch (TransformerConfigurationException t) { + throw new FOPException(t); + } + } + + /** + * + * @param result + * of transform + * @param xsltFile + * for transform + * @throws FOPException + * for general errors + */ + public TransformerNode(Result result, File xsltFile) throws FOPException { + try { + TransformerFactory transFact = TransformerFactory.newInstance(); + SAXTransformerFactory saxTFactory = ((SAXTransformerFactory)transFact); + StreamSource ss = new StreamSource(xsltFile); + transformerHandler = saxTFactory.newTransformerHandler(ss); + transformerHandler.setResult(result); + } catch (TransformerConfigurationException t) { + throw new FOPException(t); + } + } + + /** + * This is part of a two phase construction. Call this, then call + * initResult. + * + * @param xsltFile + * for transform + * @throws FOPException + * for general errors + */ + public TransformerNode(Source xsltFile) throws FOPException { + try { + TransformerFactory transFact = TransformerFactory.newInstance(); + SAXTransformerFactory saxTFactory = ((SAXTransformerFactory)transFact); + transformerHandler = saxTFactory.newTransformerHandler(xsltFile); + } catch (TransformerConfigurationException t) { + throw new FOPException(t); + } + } + + /** + * This is part of a two phase construction. Call this, then call + * initResult. + * + * @param xsltTemplates + * for transform + * @throws FOPException + * for general errors + */ + public TransformerNode(Templates xsltTemplates) throws FOPException { + try { + TransformerFactory transFact = TransformerFactory.newInstance(); + SAXTransformerFactory saxTFactory = ((SAXTransformerFactory)transFact); + transformerHandler = saxTFactory.newTransformerHandler(xsltTemplates); + } catch (TransformerConfigurationException t) { + throw new FOPException(t); + } + } + + /** + * Call this after calling constructor for xsltFile only above. + * + * @param result + * of transform + */ + public void initResult(Result result) { + transformerHandler.setResult(result); + } + + /******************** start of ContentHandler ***************************/ + /** {@inheritDoc} */ + public void setDocumentLocator(Locator locator) { + if (transformerHandler != null) { + transformerHandler.setDocumentLocator(locator); + } + } + + /** {@inheritDoc} */ + public void startDocument() throws SAXException { + if (transformerHandler != null) { + transformerHandler.startDocument(); + } + } + + /** {@inheritDoc} */ + public void endDocument() throws SAXException { + if (transformerHandler != null) { + transformerHandler.endDocument(); + } + } + + /** {@inheritDoc} */ + public void processingInstruction(String target, String data) throws SAXException { + if (transformerHandler != null) { + transformerHandler.processingInstruction(target, data); + } + } + + /** {@inheritDoc} */ + public void startElement(String uri, String local, String raw, Attributes attrs) + throws SAXException { + AttributesImpl ai = new AttributesImpl(attrs); + if (transformerHandler != null) { + transformerHandler.startElement(uri, local, raw, ai); + } + } + + /** {@inheritDoc} */ + public void characters(char[] ch, int start, int length) throws SAXException { + if (transformerHandler != null) { + transformerHandler.characters(ch, start, length); + } + } + + /** {@inheritDoc} */ + public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { + if (transformerHandler != null) { + transformerHandler.ignorableWhitespace(ch, start, length); + } + } + + /** {@inheritDoc} */ + public void endElement(String uri, String local, String raw) throws SAXException { + if (transformerHandler != null) { + transformerHandler.endElement(uri, local, raw); + } + } + + /** {@inheritDoc} */ + public void skippedEntity(String string) throws SAXException { + if (transformerHandler != null) { + transformerHandler.skippedEntity(string); + } + } + + /** {@inheritDoc} */ + public void startPrefixMapping(String string, String string1) throws SAXException { + if (transformerHandler != null) { + transformerHandler.startPrefixMapping(string, string1); + } + } + + /** {@inheritDoc} */ + public void endPrefixMapping(String string) throws SAXException { + if (transformerHandler != null) { + transformerHandler.endPrefixMapping(string); + } + } + + /***************************** LexicalHandlerImpl **************************/ + /** + * @param name + * - param1 + * @param pid + * - param2 + * @param lid + * - param3 + * @throws SAXException + * - if parser fails + */ + public void startDTD(String name, String pid, String lid) throws SAXException { + if (transformerHandler != null) { + transformerHandler.startDTD(name, pid, lid); + } + } + + /** + * End of DTD + * + * @throws SAXException + * - if parser fails + */ + public void endDTD() throws SAXException { + if (transformerHandler != null) { + transformerHandler.endDTD(); + } + } + + /** + * startEnitity. + * + * @param string + * - param 1 + * @throws SAXException + * - if parser fails + */ + public void startEntity(String string) throws SAXException { + if (transformerHandler != null) { + transformerHandler.startEntity(string); + } + } + + /** + * end Entity + * + * @param string + * - param 1 + * @throws SAXException + * - if paser fails + */ + public void endEntity(String string) throws SAXException { + if (transformerHandler != null) { + transformerHandler.endEntity(string); + } + } + + /** + * Start of CDATA section + * + * @throws SAXException + * - parser fails + */ + public void startCDATA() throws SAXException { + if (transformerHandler != null) { + transformerHandler.startCDATA(); + } + } + + /** + * endCDATA section + * + * @throws SAXException + * - if paser fails + */ + public void endCDATA() throws SAXException { + if (transformerHandler != null) { + transformerHandler.endCDATA(); + } + } + + /** + * + * @param charArray + * - the characters + * @param int1 + * - param 2 + * @param int2 + * - param 3 + * @throws SAXException + * - if paser fails + */ + public void comment(char[] charArray, int int1, int int2) throws SAXException { + if (transformerHandler != null) { + transformerHandler.comment(charArray, int1, int2); + } + } + + /******************** End of Lexical Handler ***********************/ +} diff --git a/src/java/org/apache/fop/accessibility/TransformerNodeEndProcessing.java b/src/java/org/apache/fop/accessibility/TransformerNodeEndProcessing.java new file mode 100644 index 000000000..8e02c67cd --- /dev/null +++ b/src/java/org/apache/fop/accessibility/TransformerNodeEndProcessing.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.accessibility; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Result; +import javax.xml.transform.Source; +import javax.xml.transform.Templates; +import javax.xml.transform.Transformer; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; + +/** + * An extension of TransformerNode used to run 2nd transform after completion of first + */ +class TransformerNodeEndProcessing extends TransformerNode { + + private final ByteArrayOutputStream enrichedFOBuffer = new ByteArrayOutputStream(); + private DefaultHandler delegateHandler = null; + private final FOUserAgent userAgent; + + /** + * Do a transform, but perform special processing at the end for the access + * stuff. + * + * @param xsltTemplates Transform to do. + * @param fopHandler Used in the end processing + * @param userAgent the userAgent + * @throws FOPException + * if transform fails + */ + public TransformerNodeEndProcessing(Templates xsltTemplates, DefaultHandler fopHandler, + FOUserAgent userAgent) throws FOPException { + super(xsltTemplates); + delegateHandler = fopHandler; + this.userAgent = userAgent; + Result res1 = new StreamResult(enrichedFOBuffer); + super.initResult(res1); + } + + /** + * Do a transform, but perform special processing at the end for the access + * stuff. + * + * @param xsltFile Transform to do. + * @param fopHandler Used in the end processing + * @param userAgent the userAgent + * @throws FOPException if transform fails + */ + + public TransformerNodeEndProcessing(Source xsltFile, DefaultHandler fopHandler, + FOUserAgent userAgent) throws FOPException { + super(xsltFile); + delegateHandler = fopHandler; + this.userAgent = userAgent; + Result res1 = new StreamResult(enrichedFOBuffer); + super.initResult(res1); + } + + /** {@inheritDoc} */ + public void endDocument() throws SAXException { + super.endDocument(); + // do the second transform to struct + try { + //TODO this must be optimized, no buffering (ex. SAX-based tee-proxy) + byte[] enrichedFO = enrichedFOBuffer.toByteArray(); + Transformer transformer = AccessibilityUtil.getReduceFOTreeTemplates().newTransformer(); + Source src = new StreamSource(new ByteArrayInputStream(enrichedFO)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Result res = new StreamResult(out); + transformer.transform(src, res); + userAgent.setReducedFOTree(out.toByteArray()); + + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setNamespaceAware(true); + saxParserFactory.setValidating(false); + SAXParser saxParser = saxParserFactory.newSAXParser(); + InputStream in = new ByteArrayInputStream(enrichedFO); + saxParser.parse(in, delegateHandler); + } catch (Exception e) { + // TODO Auto-generated catch block + throw new SAXException(e); + } + + } + +} diff --git a/src/java/org/apache/fop/accessibility/addPtr.xsl b/src/java/org/apache/fop/accessibility/addPtr.xsl new file mode 100644 index 000000000..f619817cd --- /dev/null +++ b/src/java/org/apache/fop/accessibility/addPtr.xsl @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<!-- $Id$ --> +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:fo="http://www.w3.org/1999/XSL/Format" xmlns:foi="http://xmlgraphics.apache.org/fop/internal"> +<xsl:template match="fo:block|fo:block-container"><xsl:call-template name="addPtr"/></xsl:template> +<xsl:template match="fo:list-block|fo:list-item|fo:list-item-label|fo:list-item-body"><xsl:call-template name="addPtr"/></xsl:template> +<xsl:template match="fo:table|fo:table-body|fo:table-header|fo:table-footer|fo:table-row|fo:table-cell"><xsl:call-template name="addPtr"/></xsl:template> +<xsl:template match="fo:inline|fo:wrapper|fo:basic-link|fo:character"><xsl:call-template name="addPtr"/></xsl:template> +<xsl:template match="fo:instream-foreign-object|fo:external-graphic"><xsl:call-template name="addPtr"/></xsl:template> +<xsl:template match="fo:page-number|fo:page-number-citation|fo:page-number-citation-last"><xsl:call-template name="addPtr"/></xsl:template> +<xsl:template match="fo:footnote|fo:footnote-body"><xsl:call-template name="addPtr"/></xsl:template> +<xsl:template match="fo:marker"><xsl:call-template name="addPtr"/></xsl:template> +<xsl:template name="addPtr"><xsl:element name="{name()}" namespace="{namespace-uri()}"><xsl:apply-templates select="@*"/><xsl:attribute name="foi:ptr"><xsl:value-of select="generate-id()"/></xsl:attribute><xsl:apply-templates/></xsl:element></xsl:template> +<xsl:template match="@*|node()"><xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy></xsl:template> +</xsl:stylesheet>
\ No newline at end of file diff --git a/src/java/org/apache/fop/accessibility/reduceFOTree.xsl b/src/java/org/apache/fop/accessibility/reduceFOTree.xsl new file mode 100644 index 000000000..eb808286b --- /dev/null +++ b/src/java/org/apache/fop/accessibility/reduceFOTree.xsl @@ -0,0 +1,86 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> +<!-- $Id$ --> +<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:fo="http://www.w3.org/1999/XSL/Format" + xmlns:fox="http://xmlgraphics.apache.org/fop/extensions" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns:foi="http://xmlgraphics.apache.org/fop/internal" + version="1.0"> + <xsl:output method="xml" indent="no"/> + + <xsl:template match="*"> + <xsl:element name="{name()}"> + <xsl:copy-of select="@foi:ptr"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + <xsl:template match="@master-reference|@flow-name"></xsl:template> + + <xsl:template match="fo:block"> + <xsl:element name="{name()}"> + <xsl:copy-of select="@foi:ptr"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + <xsl:template match="fo:inline|fo:wrapper|fo:page-number|fo:page-number-citation|fo:page-number-citation-last"> + <xsl:element name="{name()}"> + <xsl:copy-of select="@foi:ptr"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + <xsl:template match="fo:table-cell|fo:table|fo:table-body|fo:table-footer|fo:table-row|fo:table-header"> + <xsl:element name="{name()}"> + <xsl:copy-of select="@foi:ptr"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + <xsl:template match="fo:list-block|fo:list-item|fo:list-item-label|fo:list-item-body"> + <xsl:element name="{name()}"> + <xsl:copy-of select="@foi:ptr"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + <xsl:template match="fo:basic-link|fo:block-container|fo:character|fo:instream-foreign-object|fo:marker"> + <xsl:element name="{name()}"> + <xsl:copy-of select="@foi:ptr"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + <xsl:template match="fo:external-graphic|fo:instream-foreign-object"> + <xsl:element name="{name()}"> + <xsl:copy-of select="@fox:alt-text|@foi:ptr"/> + <xsl:apply-templates/> + </xsl:element> + </xsl:template> + + + + <xsl:template match="text()"></xsl:template> + + <xsl:template match="fo:layout-master-set|comment() | processing-instruction() | fo:simple-page-master | fo:table-column | fo:leader | fo:retrieve-marker "/> + + <xsl:template match="svg:svg | svg | fo:inline-container | fo:float | fo:bidi-override"/> + +</xsl:stylesheet> diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java index ebd2e0594..1e0e3fb86 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -99,6 +99,9 @@ public class FOUserAgent { private boolean locatorEnabled = true; // true by default (for error messages). private EventBroadcaster eventBroadcaster = new FOPEventBroadcaster(); + //TODO Verify that a byte array is the best solution here + private byte[] reducedFOTree; // accessibility: reduced FO + /** Producer: Metadata element for the system/software that produces * the document. (Some renderers can store this in the document.) */ @@ -153,6 +156,9 @@ public class FOUserAgent { setBaseURL(factory.getBaseURL()); setFontBaseURL(factory.getFontManager().getFontBaseURL()); setTargetResolution(factory.getTargetResolution()); + if (this.getRendererOptions().get("accessibility") == null) { + this.rendererOptions.put("accessibility", Boolean.FALSE); + } } /** @return the associated FopFactory instance */ @@ -197,6 +203,7 @@ public class FOUserAgent { return rendererOverride; } + /** * Sets an explicit FOEventHandler instance which overrides the one * defined by the render type setting. @@ -615,5 +622,34 @@ public class FOUserAgent { } + /** + * check if accessibility is enabled + * @return boolean + */ + public boolean accessibilityEnabled() { + Boolean enabled = (Boolean)this.getRendererOptions().get("accessibility"); + if (enabled != null) { + return enabled.booleanValue(); + } else { + return false; + } + } + + /** + * Used for accessibility. Stores the reduced FO tree (the result from the second transform) + * for later use. + * @param reducedFOTree the result from 2nd transform + */ + public void setReducedFOTree(byte[] reducedFOTree) { + this.reducedFOTree = reducedFOTree; + } + + /** + * Used for accessibility. Returns the reduced FO tree. + * @return result from 2nd transform as byte array + */ + public byte[] getReducedFOTree() { + return this.reducedFOTree; + } } diff --git a/src/java/org/apache/fop/apps/Fop.java b/src/java/org/apache/fop/apps/Fop.java index 0527ea290..e5927fdba 100644 --- a/src/java/org/apache/fop/apps/Fop.java +++ b/src/java/org/apache/fop/apps/Fop.java @@ -24,6 +24,7 @@ import java.io.OutputStream; import org.xml.sax.helpers.DefaultHandler; +import org.apache.fop.accessibility.AccessibilityUtil; import org.apache.fop.fo.FOTreeBuilder; /** @@ -110,7 +111,11 @@ public class Fop { if (foTreeBuilder == null) { createDefaultHandler(); } - return this.foTreeBuilder; + if (this.foUserAgent.accessibilityEnabled()) { + return AccessibilityUtil.decorateDefaultHandler(this.foTreeBuilder, foUserAgent); + } else { + return this.foTreeBuilder; + } } /** diff --git a/src/java/org/apache/fop/apps/FopFactory.java b/src/java/org/apache/fop/apps/FopFactory.java index 96c22f964..cf22a1e56 100644 --- a/src/java/org/apache/fop/apps/FopFactory.java +++ b/src/java/org/apache/fop/apps/FopFactory.java @@ -99,6 +99,11 @@ public class FopFactory implements ImageContext { * external-graphics. */ private String base = null; + + /** + * Controls if accessibility is turned on or off + */ + private boolean accessibility = false; /** The base URL for all hyphen URL resolutions. */ private String hyphenBase = null; @@ -181,10 +186,19 @@ public class FopFactory implements ImageContext { */ public FOUserAgent newFOUserAgent() { FOUserAgent userAgent = new FOUserAgent(this); + userAgent.getRendererOptions().put("accessibility", Boolean.valueOf(this.accessibility)); return userAgent; } /** + * Used for accessibility to pass value to newFOUserAgent + * @param value set through xconf file + */ + void setAccessibility(boolean value) { + this.accessibility = value; + } + + /** * Returns a new {@link Fop} instance. FOP will be configured with a default user agent * instance. * <p> diff --git a/src/java/org/apache/fop/apps/FopFactoryConfigurator.java b/src/java/org/apache/fop/apps/FopFactoryConfigurator.java index e71173845..e6f1aff16 100644 --- a/src/java/org/apache/fop/apps/FopFactoryConfigurator.java +++ b/src/java/org/apache/fop/apps/FopFactoryConfigurator.java @@ -90,6 +90,15 @@ public class FopFactoryConfigurator { log.debug("Initializing FopFactory Configuration"); } + if (cfg.getChild("accessibility", false) != null) { + try { + this.factory.setAccessibility( + cfg.getChild("accessibility").getValueAsBoolean()); + } catch (ConfigurationException e) { + throw new FOPException(e); + } + } + // strict configuration if (cfg.getChild("strict-configuration", false) != null) { try { diff --git a/src/java/org/apache/fop/area/Trait.java b/src/java/org/apache/fop/area/Trait.java index 45c0432f4..27ce39252 100644 --- a/src/java/org/apache/fop/area/Trait.java +++ b/src/java/org/apache/fop/area/Trait.java @@ -194,9 +194,12 @@ public class Trait implements Serializable { public static final Integer OVERLINE_COLOR = new Integer(35); /** Trait for color of linethrough decorations when rendering inline parent. */ public static final Integer LINETHROUGH_COLOR = new Integer(36); + + /** The ptr trait. Used for accessibility */ + public static final Integer PTR = new Integer(37); /** Maximum value used by trait keys */ - public static final int MAX_TRAIT_KEY = 36; + public static final int MAX_TRAIT_KEY = 37; private static final TraitInfo[] TRAIT_INFO = new TraitInfo[MAX_TRAIT_KEY + 1]; @@ -225,6 +228,7 @@ public class Trait implements Serializable { static { // Create a hashmap mapping trait code to name for external representation //put(ID_LINK, new TraitInfo("id-link", String.class)); + put(PTR, new TraitInfo("ptr", String.class)); put(INTERNAL_LINK, new TraitInfo("internal-link", InternalLink.class)); put(EXTERNAL_LINK, new TraitInfo("external-link", ExternalLink.class)); put(FONT, new TraitInfo("font", FontTriplet.class)); diff --git a/src/java/org/apache/fop/cli/CommandLineOptions.java b/src/java/org/apache/fop/cli/CommandLineOptions.java index fef2a1f46..734cd9b98 100644 --- a/src/java/org/apache/fop/cli/CommandLineOptions.java +++ b/src/java/org/apache/fop/cli/CommandLineOptions.java @@ -327,6 +327,8 @@ public class CommandLineOptions { i = i + parseAreaTreeOption(args, i); } else if (args[i].equals("-if")) { i = i + parseIntermediateFormatOption(args, i); + } else if (args[i].equals("-a")) { + this.renderingOptions.put("accessibility", Boolean.TRUE); } else if (args[i].equals("-v")) { printVersion(); return false; @@ -1129,6 +1131,7 @@ public class CommandLineOptions { + " -nocopy PDF file will be encrypted without copy content permission\n" + " -noedit PDF file will be encrypted without edit content permission\n" + " -noannotations PDF file will be encrypted without edit annotation permission\n" + + " -a enables accessibility features (Tagged PDF etc., default off)\n" + " -pdfprofile prof PDF file will be generated with the specified profile\n" + " (Examples for prof: PDF/A-1b or PDF/X-3:2003)\n\n" + " [INPUT] \n" diff --git a/src/java/org/apache/fop/fo/Constants.java b/src/java/org/apache/fop/fo/Constants.java index a1ccb20e7..8bcaa1538 100644 --- a/src/java/org/apache/fop/fo/Constants.java +++ b/src/java/org/apache/fop/fo/Constants.java @@ -771,8 +771,16 @@ public interface Constants { * multi-column layouts. */ int PR_X_DISABLE_COLUMN_BALANCING = 273; + /** Property constant - FOP proprietary: FOP internal use for accessibility */ + int PR_X_PTR = 274; + /** + * Property constant - FOP proprietary: alternative text for e-g and i-f-o. + * Used for accessibility. + */ + int PR_X_ALT_TEXT = 275; + /** Number of property constants defined */ - int PROPERTY_COUNT = 273; + int PROPERTY_COUNT = 275; // compound property constants diff --git a/src/java/org/apache/fop/fo/FONode.java b/src/java/org/apache/fop/fo/FONode.java index 07d8f472c..8c4e3416d 100644 --- a/src/java/org/apache/fop/fo/FONode.java +++ b/src/java/org/apache/fop/fo/FONode.java @@ -36,6 +36,7 @@ import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.extensions.InternalElementMapping; import org.apache.fop.fo.extensions.svg.SVGElementMapping; import org.apache.fop.fo.pagination.Root; import org.apache.fop.util.CharUtilities; @@ -419,6 +420,8 @@ public abstract class FONode implements Cloneable { return "fo:" + localName; } else if (namespaceURI.equals(ExtensionElementMapping.URI)) { return "fox:" + localName; + } else if (namespaceURI.equals(InternalElementMapping.URI)) { + return "foi:" + localName; // used FOP internally for accessibility } else if (namespaceURI.equals(SVGElementMapping.URI)) { return "svg:" + localName; } else { diff --git a/src/java/org/apache/fop/fo/FOPropertyMapping.java b/src/java/org/apache/fop/fo/FOPropertyMapping.java index b4a864e43..d214dee99 100644 --- a/src/java/org/apache/fop/fo/FOPropertyMapping.java +++ b/src/java/org/apache/fop/fo/FOPropertyMapping.java @@ -2515,6 +2515,18 @@ public final class FOPropertyMapping implements Constants { m.setDefault(""); addPropertyMaker("id", m); + // foi:ptr, used for accessibility + m = new StringProperty.Maker(PR_X_PTR); + m.setInherited(false); + m.setDefault(""); + addPropertyMaker("foi:ptr", m); + + // fox:alt, used for accessibility + m = new StringProperty.Maker(PR_X_ALT_TEXT); + m.setInherited(false); + m.setDefault(""); + addPropertyMaker("fox:alt-text", m); + // provisional-label-separation m = new LengthProperty.Maker(PR_PROVISIONAL_LABEL_SEPARATION); m.setInherited(true); diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java index 927bed0f7..ee2b3886e 100644 --- a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java +++ b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java @@ -46,6 +46,8 @@ public class ExtensionElementMapping extends ElementMapping { propertyAttributes.add("orphan-content-limit"); propertyAttributes.add("internal-destination"); propertyAttributes.add("disable-column-balancing"); + //These are FOP's extension properties for accessibility + propertyAttributes.add("alt"); } /** diff --git a/src/java/org/apache/fop/fo/extensions/ExternalDocument.java b/src/java/org/apache/fop/fo/extensions/ExternalDocument.java index bea6f6f61..233714bd7 100644 --- a/src/java/org/apache/fop/fo/extensions/ExternalDocument.java +++ b/src/java/org/apache/fop/fo/extensions/ExternalDocument.java @@ -31,7 +31,7 @@ import org.apache.fop.fo.pagination.AbstractPageSequence; import org.apache.fop.fo.properties.LengthRangeProperty; /** - * Class for the fox:external-document extenstion element. + * Class for the fox:external-document extension element. */ public class ExternalDocument extends AbstractPageSequence implements GraphicsProperties { diff --git a/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java b/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java new file mode 100644 index 000000000..c6802b5b3 --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java @@ -0,0 +1,85 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.fo.extensions; + +import java.util.HashMap; +import java.util.Set; + +import org.apache.xmlgraphics.util.QName; + +import org.apache.fop.fo.ElementMapping; +import org.apache.fop.fo.FONode; +import org.apache.fop.fo.UnknownXMLObj; +import org.apache.fop.fo.extensions.destination.Destination; + +/** + * Element mapping for FOP's internal extension to XSL-FO. + */ +public class InternalElementMapping extends ElementMapping { + + /** The FOP extension namespace URI */ + public static final String URI = "http://xmlgraphics.apache.org/fop/internal"; + + private static final Set propertyAttributes = new java.util.HashSet(); + + static { + //These are FOP's extension properties for accessibility + propertyAttributes.add("ptr"); + } + + /** + * Constructor. + */ + public InternalElementMapping() { + namespaceURI = URI; + } + + /** + * Initialize the data structures. + */ + protected void initialize() { + if (foObjs == null) { + foObjs = new HashMap(); + } + } + + /* static class DestinationMaker extends ElementMapping.Maker { + public FONode make(FONode parent) { + return new Destination(parent); + } + } */ + + + /** + * used internally for accessibility + */ + public String getStandardPrefix() { + return "foi"; + } + + /** {@inheritDoc} */ + public boolean isAttributeProperty(QName attributeName) { + if (!URI.equals(attributeName.getNamespaceURI())) { + throw new IllegalArgumentException("The namespace URIs don't match"); + } + return propertyAttributes.contains(attributeName.getLocalName()); + } + +} diff --git a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java index d72682282..2ed576a32 100644 --- a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java +++ b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java @@ -60,6 +60,7 @@ public abstract class AbstractGraphics extends FObj implements GraphicsPropertie private int scaling; private int textAlign; private Length width; + private String ptr; // used for accessibility // Unused but valid items, commented out for performance: // private CommonAccessibility commonAccessibility; // private CommonAural commonAural; @@ -94,6 +95,7 @@ public abstract class AbstractGraphics extends FObj implements GraphicsPropertie dominantBaseline = pList.get(PR_DOMINANT_BASELINE).getEnum(); height = pList.get(PR_HEIGHT).getLength(); id = pList.get(PR_ID).getString(); + ptr = pList.get(PR_X_PTR).getString(); // used for accessibility inlineProgressionDimension = pList.get(PR_INLINE_PROGRESSION_DIMENSION).getLengthRange(); keepWithNext = pList.get(PR_KEEP_WITH_NEXT).getKeep(); keepWithPrevious = pList.get(PR_KEEP_WITH_PREVIOUS).getKeep(); @@ -207,6 +209,11 @@ public abstract class AbstractGraphics extends FObj implements GraphicsPropertie return keepWithPrevious; } + /** @return the "foi:ptr" property. */ + public String getPtr() { + return ptr; + } + /** @return the graphic's intrinsic width in millipoints */ public abstract int getIntrinsicWidth(); diff --git a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java index 5f420efb8..3554c8e0e 100644 --- a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java +++ b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java @@ -51,6 +51,7 @@ public abstract class AbstractPageNumberCitation extends FObj { private int alignmentBaseline; private Length baselineShift; private int dominantBaseline; + private String ptr; // used for accessibility // private ToBeImplementedProperty letterSpacing; private SpaceProperty lineHeight; private String refId; @@ -96,6 +97,7 @@ public abstract class AbstractPageNumberCitation extends FObj { dominantBaseline = pList.get(PR_DOMINANT_BASELINE).getEnum(); // letterSpacing = pList.get(PR_LETTER_SPACING); lineHeight = pList.get(PR_LINE_HEIGHT).getSpace(); + ptr = pList.get(PR_X_PTR).getString(); // used for accessibility refId = pList.get(PR_REF_ID).getString(); textDecoration = pList.getTextDecorationProps(); // textShadow = pList.get(PR_TEXT_SHADOW); @@ -138,6 +140,11 @@ public abstract class AbstractPageNumberCitation extends FObj { return textDecoration; } + /** @return the "foi:ptr" property. */ + public String getPtr() { + return ptr; + } + /** @return the "alignment-adjust" property */ public Length getAlignmentAdjust() { return alignmentAdjust; diff --git a/src/java/org/apache/fop/fo/flow/BasicLink.java b/src/java/org/apache/fop/fo/flow/BasicLink.java index ee3171188..2fe0c9a8d 100644 --- a/src/java/org/apache/fop/fo/flow/BasicLink.java +++ b/src/java/org/apache/fop/fo/flow/BasicLink.java @@ -65,6 +65,7 @@ public class BasicLink extends Inline { /** {@inheritDoc} */ public void bind(PropertyList pList) throws FOPException { super.bind(pList); + // destinationPlacementOffset = pList.get(PR_DESTINATION_PLACEMENT_OFFSET); externalDestination = pList.get(PR_EXTERNAL_DESTINATION).getString(); // indicateDestination = pList.get(PR_INDICATE_DESTINATION); @@ -158,6 +159,11 @@ public class BasicLink extends Inline { return this.showDestination; } + /** @return the "foi:ptr" property. */ + public String getPtr() { + return super.getPtr(); + } + /** {@inheritDoc} */ public String getLocalName() { return "basic-link"; diff --git a/src/java/org/apache/fop/fo/flow/Block.java b/src/java/org/apache/fop/fo/flow/Block.java index daaebd6d0..efcd18b71 100644 --- a/src/java/org/apache/fop/fo/flow/Block.java +++ b/src/java/org/apache/fop/fo/flow/Block.java @@ -71,6 +71,7 @@ public class Block extends FObjMixed implements BreakPropertySet { private int lineHeightShiftAdjustment; private int lineStackingStrategy; private Numeric orphans; + private String ptr; //used for accessibility private int whiteSpaceTreatment; private int span; private int textAlign; @@ -122,6 +123,7 @@ public class Block extends FObjMixed implements BreakPropertySet { lineHeightShiftAdjustment = pList.get(PR_LINE_HEIGHT_SHIFT_ADJUSTMENT).getEnum(); lineStackingStrategy = pList.get(PR_LINE_STACKING_STRATEGY).getEnum(); orphans = pList.get(PR_ORPHANS).getNumeric(); + ptr = pList.get(PR_X_PTR).getString(); //used for accessibility whiteSpaceTreatment = pList.get(PR_WHITE_SPACE_TREATMENT).getEnum(); span = pList.get(PR_SPAN).getEnum(); textAlign = pList.get(PR_TEXT_ALIGN).getEnum(); @@ -171,6 +173,11 @@ public class Block extends FObjMixed implements BreakPropertySet { return breakAfter; } + /** @return the "foi:ptr" property. */ + public String getPtr() { + return ptr; + } + /** @return the "break-before" property. */ public int getBreakBefore() { return breakBefore; diff --git a/src/java/org/apache/fop/fo/flow/Character.java b/src/java/org/apache/fop/fo/flow/Character.java index f76b3225d..636b2b1c7 100644 --- a/src/java/org/apache/fop/fo/flow/Character.java +++ b/src/java/org/apache/fop/fo/flow/Character.java @@ -62,6 +62,7 @@ public class Character extends FObj { private CommonTextDecoration textDecoration; // private ToBeImplementedProperty textShadow; private Property wordSpacing; + private String ptr; // used for accessibility // Unused but valid items, commented out for performance: // private CommonAural commonAural; // private CommonMarginInline commonMarginInline; @@ -108,6 +109,7 @@ public class Character extends FObj { lineHeight = pList.get(PR_LINE_HEIGHT).getSpace(); textDecoration = pList.getTextDecorationProps(); wordSpacing = pList.get(PR_WORD_SPACING); + ptr = pList.get(PR_X_PTR).getString(); // used for accessibility } /** {@inheritDoc} */ @@ -207,6 +209,11 @@ public class Character extends FObj { return keepWithPrevious; } + /** @return the "foi:ptr" property. */ + public String getPtr() { + return ptr; + } + /** {@inheritDoc} */ public String getLocalName() { return "character"; diff --git a/src/java/org/apache/fop/fo/flow/Inline.java b/src/java/org/apache/fop/fo/flow/Inline.java index dae7d306b..ea5b49baf 100644 --- a/src/java/org/apache/fop/fo/flow/Inline.java +++ b/src/java/org/apache/fop/fo/flow/Inline.java @@ -37,6 +37,7 @@ public class Inline extends InlineLevel { private Length alignmentAdjust; private int alignmentBaseline; private Length baselineShift; + private String ptr; // used for accessibility private int dominantBaseline; // Unused but valid items, commented out for performance: // private CommonRelativePosition commonRelativePosition; @@ -66,6 +67,7 @@ public class Inline extends InlineLevel { alignmentBaseline = pList.get(PR_ALIGNMENT_BASELINE).getEnum(); baselineShift = pList.get(PR_BASELINE_SHIFT).getLength(); dominantBaseline = pList.get(PR_DOMINANT_BASELINE).getEnum(); + ptr = pList.get(PR_X_PTR).getString(); // used for accessibility } /** {@inheritDoc} */ @@ -144,6 +146,11 @@ public class Inline extends InlineLevel { return dominantBaseline; } + /** @return the "foi:ptr" property. */ + public String getPtr() { + return ptr; + } + /** {@inheritDoc} */ public String getLocalName() { return "inline"; diff --git a/src/java/org/apache/fop/fo/flow/PageNumber.java b/src/java/org/apache/fop/fo/flow/PageNumber.java index dc834d708..bb251f44f 100644 --- a/src/java/org/apache/fop/fo/flow/PageNumber.java +++ b/src/java/org/apache/fop/fo/flow/PageNumber.java @@ -47,6 +47,7 @@ public class PageNumber extends FObj { private int alignmentBaseline; private Length baselineShift; private int dominantBaseline; + private String ptr; // used for accessibility // private ToBeImplementedProperty letterSpacing; private SpaceProperty lineHeight; /** Holds the text decoration values. May be null */ @@ -92,6 +93,7 @@ public class PageNumber extends FObj { // letterSpacing = pList.get(PR_LETTER_SPACING); lineHeight = pList.get(PR_LINE_HEIGHT).getSpace(); textDecoration = pList.getTextDecorationProps(); + ptr = pList.get(PR_X_PTR).getString(); // used for accessibility // textShadow = pList.get(PR_TEXT_SHADOW); // implicit properties @@ -165,6 +167,11 @@ public class PageNumber extends FObj { return lineHeight; } + /** @return the "foi:ptr" property. */ + public String getPtr() { + return ptr; + } + /** {@inheritDoc} */ public String getLocalName() { return "page-number"; diff --git a/src/java/org/apache/fop/fo/flow/table/TableFObj.java b/src/java/org/apache/fop/fo/flow/table/TableFObj.java index ec508580c..96b3769a3 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableFObj.java +++ b/src/java/org/apache/fop/fo/flow/table/TableFObj.java @@ -46,6 +46,7 @@ public abstract class TableFObj extends FObj { private Numeric borderBeforePrecedence; private Numeric borderEndPrecedence; private Numeric borderStartPrecedence; + private String ptr; ConditionalBorder borderBefore; ConditionalBorder borderAfter; @@ -71,6 +72,7 @@ public abstract class TableFObj extends FObj { borderBeforePrecedence = pList.get(PR_BORDER_BEFORE_PRECEDENCE).getNumeric(); borderEndPrecedence = pList.get(PR_BORDER_END_PRECEDENCE).getNumeric(); borderStartPrecedence = pList.get(PR_BORDER_START_PRECEDENCE).getNumeric(); + ptr = pList.get(PR_X_PTR).getString(); if (getNameId() != FO_TABLE //Separate check for fo:table in Table.java && getNameId() != FO_TABLE_CELL && getCommonBorderPaddingBackground().hasPadding( @@ -235,6 +237,11 @@ public abstract class TableFObj extends FObj { } } + /** @return the "foi:ptr" property. */ + public String getPtr() { + return ptr; + } + /** * Prepares the borders of this element if the collapsing-border model is in use. * Conflict resolution with parent elements is done where applicable. diff --git a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java index c641c3e69..acfcbe3f0 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java @@ -385,6 +385,7 @@ public class BlockLayoutManager extends BlockStackingLayoutManager addMarkersToPage(false, isFirst(firstPos), isLast(lastPos)); + TraitSetter.addPtr(curBlockArea, getBlockFO().getPtr()); // used for accessibility TraitSetter.addSpaceBeforeAfter(curBlockArea, layoutContext.getSpaceAdjust(), effSpaceBefore, effSpaceAfter); flush(); diff --git a/src/java/org/apache/fop/layoutmgr/TraitSetter.java b/src/java/org/apache/fop/layoutmgr/TraitSetter.java index 9cab6322b..d062ba2ca 100644 --- a/src/java/org/apache/fop/layoutmgr/TraitSetter.java +++ b/src/java/org/apache/fop/layoutmgr/TraitSetter.java @@ -584,6 +584,17 @@ public class TraitSetter { } /** + * Adds the ptr trait to the area. + * @param area the area to set the traits on + * @param ptr string + */ + public static void addPtr(Area area, String ptr) { + if (ptr != null && ptr.length() > 0) { + area.addTrait(Trait.PTR, ptr); + } + } + + /** * Sets the producer's ID as a trait on the area. This can be used to track back the * generating FO node. * @param area the area to set the traits on diff --git a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java index 00c628a40..fd4d803af 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java @@ -85,6 +85,7 @@ public abstract class AbstractGraphicsLayoutManager extends LeafNodeLayoutManage transferForeignAttributes(viewportArea); Viewport vp = new Viewport(viewportArea); + TraitSetter.addPtr(vp, fobj.getPtr()); // used for accessibility TraitSetter.setProducerID(vp, fobj.getId()); vp.setIPD(imageLayout.getViewportSize().width); vp.setBPD(imageLayout.getViewportSize().height); diff --git a/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java index b65978453..a74fac743 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java @@ -138,6 +138,7 @@ public abstract class AbstractPageNumberCitationLayoutManager extends LeafNodeLa text.setBaselineOffset(font.getAscender()); TraitSetter.addFontTraits(text, font); text.addTrait(Trait.COLOR, fobj.getColor()); + TraitSetter.addPtr(text, fobj.getPtr()); // used for accessibility TraitSetter.addTextDecoration(text, fobj.getTextDecoration()); } diff --git a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java index c369df82b..e1f7475e9 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java @@ -56,6 +56,7 @@ public class BasicLinkLayoutManager extends InlineLayoutManager { private void setupBasicLinkArea(InlineArea area) { BasicLink fobj = (BasicLink) this.fobj; // internal destinations take precedence: + area.addTrait(Trait.PTR, fobj.getPtr()); // used for accessibility if (fobj.hasInternalDestination()) { String idref = fobj.getInternalDestination(); PageSequenceLayoutManager pslm = getPSLM(); diff --git a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java index 383ca0105..dcd993487 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java @@ -86,6 +86,7 @@ public class CharacterLayoutManager extends LeafNodeLayoutManager { } TraitSetter.setProducerID(text, node.getId()); TraitSetter.addTextDecoration(text, node.getTextDecoration()); + TraitSetter.addPtr(text, node.getPtr()); // used for accessibility return text; } diff --git a/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java index dc8a020ae..5cae07207 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java @@ -85,7 +85,7 @@ public class PageNumberLayoutManager extends LeafNodeLayoutManager { text.setBaselineOffset(font.getAscender()); TraitSetter.addFontTraits(text, font); text.addTrait(Trait.COLOR, fobj.getColor()); - + TraitSetter.addPtr(text, fobj.getPtr()); // used for accessibility TraitSetter.addTextDecoration(text, fobj.getTextDecoration()); return text; diff --git a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java index 27ed38b53..5c24dbfcb 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java @@ -25,10 +25,12 @@ import java.util.ListIterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.apache.fop.area.Trait; import org.apache.fop.area.inline.TextArea; import org.apache.fop.fo.Constants; import org.apache.fop.fo.FOText; +import org.apache.fop.fo.FObj; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontSelector; import org.apache.fop.layoutmgr.InlineKnuthSequence; @@ -504,12 +506,28 @@ public class TextLayoutManager extends LeafNodeLayoutManager { } TraitSetter.addFontTraits(textArea, font); textArea.addTrait(Trait.COLOR, this.foText.getColor()); - + textArea.addTrait(Trait.PTR, getPtr()); // used for accessibility TraitSetter.addTextDecoration(textArea, this.foText.getTextDecoration()); return textArea; } + /** + * used for accessibility + * @return ptr of fobj + */ + private String getPtr() { + FObj fobj = this.parentLM.getFObj(); + if (fobj instanceof org.apache.fop.fo.flow.Block) { + return (((org.apache.fop.fo.flow.Block) fobj).getPtr()); + } else if (fobj instanceof org.apache.fop.fo.flow.Inline) { + return (((org.apache.fop.fo.flow.Inline) fobj).getPtr()); + } else { + log.warn("Accessibility: TLM.getPtr-no Ptr found"); + return ""; + } + } + private void addToLetterAdjust(final int index, final int width) { if (this.letterAdjustArray[index] == null) { this.letterAdjustArray[index] = new MinOptMax(width); diff --git a/src/java/org/apache/fop/pdf/PDFArray.java b/src/java/org/apache/fop/pdf/PDFArray.java index 7c5f8ba9b..a7dfc388e 100644 --- a/src/java/org/apache/fop/pdf/PDFArray.java +++ b/src/java/org/apache/fop/pdf/PDFArray.java @@ -107,6 +107,15 @@ public class PDFArray extends PDFObject { } /** + * Indicates whether the given object exists in the array. + * @param obj the object to look for + * @return true if obj is contained + */ + public boolean contains(Object obj) { + return this.values.contains(obj); + } + + /** * Returns the length of the array * @return the length of the array */ diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index 0a4516ce6..087ae4277 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -177,18 +177,26 @@ public class PDFFactory { * @param pageWidth width of the page in points * @param pageHeight height of the page in points * @param pageIndex index of the page (zero-based) + * @param currentPageParentKey the integer key in the structural parent tree * * @return the created /Page object */ public PDFPage makePage(PDFResources resources, - int pageWidth, int pageHeight, int pageIndex) { - + int pageWidth, int pageHeight, int pageIndex, + int currentPageParentKey) { /* * create a PDFPage with the next object number, the given * resources, contents and dimensions */ - PDFPage page = new PDFPage(resources, + PDFPage page = new PDFPage(resources, // old numPages pageWidth, pageHeight, pageIndex); + if (currentPageParentKey > -1) { + //Accessibility is enabled + page.setStructParents(currentPageParentKey); + //This is a PDF 1.5 feature. It is set as a work-around for a bug in Adobe Acrobat + //which reports this missing even if the PDF file is PDF 1.4. + page.setTabs(new PDFName("S")); + } getDocument().assignObjectNumber(page); getDocument().getPages().addPage(page); @@ -203,6 +211,23 @@ public class PDFFactory { * @param resources resources object to use * @param pageWidth width of the page in points * @param pageHeight height of the page in points + * @param pageIndex index of the page (zero-based) + * + * @return the created /Page object + */ + public PDFPage makePage(PDFResources resources, + int pageWidth, int pageHeight, int pageIndex) { + return makePage(resources, pageWidth, pageHeight, pageIndex, -1); + } + + /** + * Make a /Page object. The page is assigned an object number immediately + * so references can already be made. The page must be added to the + * PDFDocument later using addObject(). + * + * @param resources resources object to use + * @param pageWidth width of the page in points + * @param pageHeight height of the page in points * * @return the created /Page object */ @@ -868,6 +893,17 @@ public class PDFFactory { } /** + * Creates and returns a StructTreeRoot object. Used for accessibility. + * @return structure Tree Root element + */ + public PDFStructTreeRoot makeStructTreeRoot() { + PDFStructTreeRoot structTreeRoot = new PDFStructTreeRoot(); + getDocument().assignObjectNumber(structTreeRoot); + getDocument().addTrailerObject(structTreeRoot); + return structTreeRoot; + } + + /** * Make a the head object of the name dictionary (the /Dests object). * * @param destinationList a list of PDFDestination instances diff --git a/src/java/org/apache/fop/pdf/PDFLink.java b/src/java/org/apache/fop/pdf/PDFLink.java index a7c4c6548..620e5d51d 100644 --- a/src/java/org/apache/fop/pdf/PDFLink.java +++ b/src/java/org/apache/fop/pdf/PDFLink.java @@ -42,6 +42,7 @@ public class PDFLink extends PDFObject { private float bry; private String color; private PDFAction action; + private Integer structParent; /** * create objects associated with a link annotation (GoToR) @@ -68,6 +69,15 @@ public class PDFLink extends PDFObject { this.action = action; } + + /** + * Used for accessibility + * @param mcid of this structParent + */ + public void setStructParent(int mcid) { + this.structParent = new Integer(mcid); + } + /** * {@inheritDoc} */ @@ -87,6 +97,8 @@ public class PDFLink extends PDFObject { + (brx) + " " + (bry) + " ]\n" + "/C [ " + this.color + " ]\n" + "/Border [ 0 0 0 ]\n" + "/A " + this.action.getAction() + "\n" + "/H /I\n" + + (this.structParent != null + ? "/StructParent " + this.structParent.toString() + "\n" : "") + fFlag + "\n>>\nendobj\n"; return s; } diff --git a/src/java/org/apache/fop/pdf/PDFPage.java b/src/java/org/apache/fop/pdf/PDFPage.java index d1472e154..6cc8c3e57 100644 --- a/src/java/org/apache/fop/pdf/PDFPage.java +++ b/src/java/org/apache/fop/pdf/PDFPage.java @@ -146,4 +146,21 @@ public class PDFPage extends PDFResourceContext { return this.pageIndex; } + /** + * Sets the "StructParents" value. + * @param structParents the integer key of this object's entry in the structural parent tree. + */ + public void setStructParents(int structParents) { + put("StructParents", structParents); + } + + /** + * Specifies the tab order for annotations on a page. + * @param value one of the allowed values (see PDF 1.5) + * @since PDF 1.5 + */ + public void setTabs(PDFName value) { + put("Tabs", value); + } + } diff --git a/src/java/org/apache/fop/pdf/PDFParentTree.java b/src/java/org/apache/fop/pdf/PDFParentTree.java new file mode 100644 index 000000000..7528aa299 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFParentTree.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +/** + * Class representing a PDF /ParentTree + */ +public class PDFParentTree extends PDFNumberTreeNode { + + /** + * Create the /ParentTree NumberTreeNode + */ + public PDFParentTree() { + super(); + } + + /** + * Get the parentTree. + * @return parentTree as PDFNumsArray + */ + public PDFNumsArray getNums() { + PDFNumsArray nums = super.getNums(); + if (nums == null) { + nums = new PDFNumsArray(this); + setNums(nums); + } + return nums; + } +} + + + + diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java index 1ea316390..c8d94585c 100644 --- a/src/java/org/apache/fop/pdf/PDFRoot.java +++ b/src/java/org/apache/fop/pdf/PDFRoot.java @@ -63,7 +63,7 @@ public class PDFRoot extends PDFDictionary { */ public PDFRoot(int objnum, PDFPages pages) { super(); - setObjectNumber(objnum); + setObjectNumber(objnum); put("Type", new PDFName("Catalog")); setRootPages(pages); } @@ -252,4 +252,32 @@ public class PDFRoot extends PDFDictionary { put("Lang", lang); } + /** + * Sets the StructTreeRoot object. Used for accessibility. + * @param structTreeRoot of this document + */ + public void setStructTreeRoot(PDFStructTreeRoot structTreeRoot) { + if (structTreeRoot == null) { + throw new NullPointerException("structTreeRoot must not be null"); + } + put("StructTreeRoot", structTreeRoot); + } + + /** + * Returns the StructTreeRoot object. + * @return the structure tree root (or null if accessibility is not enabled) + */ + public PDFStructTreeRoot getStructTreeRoot() { + return (PDFStructTreeRoot)get("StructTreeRoot"); + } + + /** + * Marks this document as conforming to the Tagged PDF conventions. + */ + public void makeTagged() { + PDFDictionary dict = new PDFDictionary(); + dict.put("Marked", Boolean.TRUE); + put("MarkInfo", dict); //new PDFMarkInfo() + } + } diff --git a/src/java/org/apache/fop/pdf/PDFStructElem.java b/src/java/org/apache/fop/pdf/PDFStructElem.java new file mode 100644 index 000000000..408b1f2c7 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFStructElem.java @@ -0,0 +1,196 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +/** + * Class representing a PDF Structure Element. + */ +public class PDFStructElem extends PDFDictionary { + + private PDFObject parentObject = null; + private String source = ""; + private boolean level1 = false; + + /** + * Create the /StructTreeRoot dictionary + * @param fo passed in fo object + * @param parent Parent of this PDFStructElem + */ + public PDFStructElem(String fo, PDFObject parent) { + super(); + if (parent instanceof PDFStructElem) { + parentObject = (PDFStructElem) parent; + } + put("Type", new PDFName("StructElem")); + source = fo; + //TODO Move this into the render/pdf package. The PDF library shall not contain FO knowledge + if ("block".equals(fo)) { + put("S", new PDFName("P")); + } else if ("inline".equals(fo) || "wrapper".equals(fo) || "character".equals(fo)) { + put("S", new PDFName("Span")); + } else if ("table-cell".equals(fo)) { + PDFStructElem grandParent = (PDFStructElem) + ((PDFStructElem)parent).getParentStructElem(); + String s = grandParent.getSource(); + if ("table-header".equals(s)) { + put("S", new PDFName("TH")); + } else { + put("S", new PDFName("TD")); + } + } else if ("table-row".equals(fo)) { + put("S", new PDFName("TR")); + } else if ("root".equals(fo)) { + put("S", new PDFName("Document")); + } else if ("page-sequence".equals(fo)) { + put("S", new PDFName("Part")); + } else if ("flow".equals(fo) || "static-content".equals(fo)) { + put("S", new PDFName("Sect")); + } else if ("page-number".equals(fo) || "page-number-citation".equals(fo) + || "page-number-citation-last".equals(fo)) { + put("S", new PDFName("Quote")); + } else if ("external-graphic".equals(fo) || "instream-foreign-object".equals(fo)) { + put("S", new PDFName("Figure")); + } else if ("table".equals(fo)) { + put("S", new PDFName("Table")); + } else if ("table-body".equals(fo)) { + put("S", new PDFName("TBody")); + } else if ("table-header".equals(fo)) { + put("S", new PDFName("THead")); + } else if ("table-footer".equals(fo)) { + put("S", new PDFName("TFoot")); + } else if ("list-block".equals(fo)) { + put("S", new PDFName("L")); + } else if ("list-item".equals(fo)) { + put("S", new PDFName("LI")); + } else if ("list-item-label".equals(fo)) { + put("S", new PDFName("Lbl")); + } else if ("list-item-body".equals(fo)) { + put("S", new PDFName("LBody")); + } else if ("block-container".equals(fo)) { + put("S", new PDFName("Div")); + } else if ("basic-link".equals(fo)) { + put("S", new PDFName("Link")); + } else if ("footnote".equals(fo)) { + put("S", new PDFName("Note")); + } else if ("footnote-body".equals(fo)) { + put("S", new PDFName("Sect")); + } else if ("marker".equals(fo)) { + put("S", new PDFName("Private")); + } else { + log.error("Accessibility: PDFStructElem constructor is missing: " + fo); + } + setParent(parent); + if (!"external-graphic".equals(fo) && !"instream-foreign-object".equals(fo)) { + put("K", new PDFArray()); + } + } + + /** + * This method is called for PDFStructElements which are direct children of + * fo:static-content or fo:flow-section + */ + public void setLevel1() { + this.level1 = true; + } + + /** + * + * @return true if the PDFStructElement is a direct child of + * fo:static-content or fo:flow-section + */ + public boolean getLevel1() { + return this.level1; + } + + /** + * Get the parent + * @return PDFStructElem of parent + */ + public PDFObject getParentStructElem() { + return (PDFStructElem)this.parentObject; + } + + /** + * Set the parent for this StructElem + * @param parent to be added + */ + public void setParent(PDFObject parent) { + if (parent != null) { + put("P", new PDFReference(parent)); + } + } + + /** + * Get the source of this StructElem + * @return the source + */ + public String getSource() { + return source; + } + + /** + * The kids of this StructElem + * @return the kids + */ + public PDFArray getKids() { + return (PDFArray)get("K"); + } + + /** + * Add a kid to this strucElem + * @param kid to be added + */ + public void addKid(PDFObject kid) { + getKids().add(kid); + } + + /** + * Add a kid, but only if it does not already exist + * @param kid to be added + * @return true if kid did not already exist + */ + public boolean addUniqueKid(PDFObject kid) { + PDFArray mArray = getKids(); + if (!mArray.contains(kid)) { + getKids().add(kid); + return true; + } else { + return false; + } + } + + /** + * Add kid referenced through mcid integer + * used fo:external-graphic + * @param mcid of this kid + */ + public void addMCIDKid(int mcid) { + put("K", mcid); + } + + /** + * Add a page reference to this structElem + * @param pageObject to be added + */ + public void addPage(Object pageObject) { + put("Pg", (PDFObject) pageObject); + } + +} diff --git a/src/java/org/apache/fop/pdf/PDFStructTreeRoot.java b/src/java/org/apache/fop/pdf/PDFStructTreeRoot.java new file mode 100644 index 000000000..e530b1582 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFStructTreeRoot.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.pdf; + +/** + * Class representing a PDF /StructTreeRoot dictionary. + */ +public class PDFStructTreeRoot extends PDFDictionary { + + /** + * Create the /StructTreeRoot dictionary. + */ + public PDFStructTreeRoot() { + super(); + put("Type", new PDFName("StructTreeRoot")); + put("K", new PDFArray()); + } + + /** + * Add parentTree entry. + * @param parentTree to be added + */ + public void addParentTree(PDFParentTree parentTree) { + put("ParentTree", parentTree); + } + + /** + * Get the kids. + * @return the kids + */ + public PDFArray getKids() { + return (PDFArray)get("K"); + } + + /** + * Returns the first child of the kids array (normally the structure tree root element) + * @return the first child + */ + public PDFObject getFirstChild() { + return (PDFObject)getKids().get(0); + } + + /** + * Adds a kid. + * @param kid to be added + */ + public void addKid(PDFObject kid) { + getKids().add(kid); + } +}
\ No newline at end of file diff --git a/src/java/org/apache/fop/pdf/PDFTextUtil.java b/src/java/org/apache/fop/pdf/PDFTextUtil.java index 6640f9b80..bb8816995 100644 --- a/src/java/org/apache/fop/pdf/PDFTextUtil.java +++ b/src/java/org/apache/fop/pdf/PDFTextUtil.java @@ -48,6 +48,7 @@ public abstract class PDFTextUtil { public static final int TR_CLIP = 7; private boolean inTextObject = false; + private boolean artifactMode = false; private String startText; private String endText; private boolean useMultiByte; @@ -116,6 +117,15 @@ public abstract class PDFTextUtil { } /** + * Indicates whether we are in a text object and if that text object represents + * an artifact. + * @return true if in artifact-mode text object + */ + public boolean inArtifactMode() { + return this.artifactMode; + } + + /** * Called when a new text object should be started. Be sure to call setFont() before * issuing any text painting commands. */ @@ -128,11 +138,51 @@ public abstract class PDFTextUtil { } /** + * Begin of a regular text object, used for accessibility + * @param mcid of text object + * @param structElemType of parent + */ + public void beginTextObjectAccess(int mcid, String structElemType) { + if (inTextObject) { + throw new IllegalStateException("Already in text object"); + } + write(structElemType + " <</MCID " + + String.valueOf(mcid) + ">>\nBDC\nBT\n"); + this.inTextObject = true; + } + + /** + * Begin of a text object marked as artifact (fo:leader in XSL-FO) text object. + * Used for accessibility. + */ + public void beginArtifactTextObject() { + if (inTextObject) { + throw new IllegalStateException("Already in text object"); + } + write("/Artifact\nBMC\nBT\n"); + this.inTextObject = true; + this.artifactMode = true; + } + + /** * Called when a text object should be ended. */ public void endTextObject() { + endTextObject(false); + } + + /** + * Called when a text object should be ended. + * @param accessEnabled indicating if accessibility is turned on or not + */ + public void endTextObject(boolean accessEnabled) { checkInTextObject(); - write("ET\n"); + if (accessEnabled) { + write("ET\nEMC\n"); + } else { + write("ET\n"); + } + this.artifactMode = false; this.inTextObject = false; initValues(); } diff --git a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java index 54f6170db..8da4b3973 100644 --- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java +++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java @@ -834,8 +834,10 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param url the URI/URL of the image * @param pos the position of the image * @param foreignAttributes an optional Map with foreign attributes, may be null + * @param ptr used for accessibility */ - protected abstract void drawImage(String url, Rectangle2D pos, Map foreignAttributes); + protected abstract void drawImage(String url, Rectangle2D pos, Map foreignAttributes, + String ptr); /** * Draw an image at the indicated location. @@ -843,7 +845,7 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param pos the position of the image */ protected final void drawImage(String url, Rectangle2D pos) { - drawImage(url, pos, null); + drawImage(url, pos, null, ""); } /** diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index 943c8c9da..d7df7596d 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -737,11 +737,13 @@ public abstract class AbstractRenderer currentBPPosition += viewport.getOffset(); Rectangle2D contpos = viewport.getContentPosition(); if (content instanceof Image) { - renderImage((Image) content, contpos); + String ptr = (String) viewport.getTrait(Trait.PTR); + renderImage((Image) content, contpos, ptr); } else if (content instanceof Container) { renderContainer((Container) content); } else if (content instanceof ForeignObject) { - renderForeignObject((ForeignObject) content, contpos); + String ptr = (String) viewport.getTrait(Trait.PTR); + renderForeignObject((ForeignObject) content, contpos, ptr); } else if (content instanceof InlineBlockParent) { renderInlineBlockParent((InlineBlockParent) content); } @@ -754,9 +756,10 @@ public abstract class AbstractRenderer * * @param image The image * @param pos The target position of the image + * @param ptr used for accessibility * (todo) Make renderImage() protected */ - public void renderImage(Image image, Rectangle2D pos) { + public void renderImage(Image image, Rectangle2D pos, String ptr) { // Default: do nothing. // Some renderers (ex. Text) don't support images. } @@ -780,9 +783,10 @@ public abstract class AbstractRenderer * * @param fo The foreign object area * @param pos The target position of the foreign object + * @param ptr used for accessibility * (todo) Make renderForeignObject() protected */ - protected void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + protected void renderForeignObject(ForeignObject fo, Rectangle2D pos, String ptr) { // Default: do nothing. // Some renderers (ex. Text) don't support foreign objects. } diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index 0c2501b87..1816ff515 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -183,7 +183,7 @@ public class AFPPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { String name = documentHandler.getPageSegmentNameFor(uri); if (name != null) { float[] srcPts = {rect.x, rect.y}; @@ -195,7 +195,7 @@ public class AFPPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { drawImageUsingDocument(doc, rect); } @@ -312,7 +312,7 @@ public class AFPPainter extends AbstractIFPainter { /** {@inheritDoc} */ public void drawText(int x, int y, final int letterSpacing, final int wordSpacing, final int[] dx, - final String text) throws IFException { + final String text, final String ptr) throws IFException { final int fontSize = this.state.getFontSize(); getPaintingState().setFontSize(fontSize); diff --git a/src/java/org/apache/fop/render/afp/AFPRenderer.java b/src/java/org/apache/fop/render/afp/AFPRenderer.java index d1d2bec3b..1967ae607 100644 --- a/src/java/org/apache/fop/render/afp/AFPRenderer.java +++ b/src/java/org/apache/fop/render/afp/AFPRenderer.java @@ -375,7 +375,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust ImageFlavor.GRAPHICS2D, ImageFlavor.BUFFERED_IMAGE, ImageFlavor.RENDERED_IMAGE }; /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + public void drawImage(String uri, Rectangle2D pos, Map foreignAttributes, String ptr) { uri = URISpecification.getURL(uri); paintingState.setImageUri(uri); @@ -506,7 +506,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust /** {@inheritDoc} */ public void renderImage(Image image, Rectangle2D pos) { - drawImage(image.getURL(), pos, image.getForeignAttributes()); + drawImage(image.getURL(), pos, image.getForeignAttributes(),""); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/intermediate/IFConstants.java b/src/java/org/apache/fop/render/intermediate/IFConstants.java index e7f7e1a00..4c9b9fc8d 100644 --- a/src/java/org/apache/fop/render/intermediate/IFConstants.java +++ b/src/java/org/apache/fop/render/intermediate/IFConstants.java @@ -50,4 +50,6 @@ public interface IFConstants extends XMLConstants { String EL_BORDER_RECT = "border-rect"; String EL_FONT = "font"; String EL_TEXT = "text"; + /** used for accessibility */ + String EL_STRUCTURE_TREE = "structure-tree"; } diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java index 6369b0251..163fe16a1 100644 --- a/src/java/org/apache/fop/render/intermediate/IFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -153,10 +153,11 @@ public interface IFPainter { * @param wordSpacing additional spacing between words (may be 0) * @param dx an array of adjustment values for each character in X-direction (may be null) * @param text the text + * @param ptr used for accessibility * @throws IFException if an error occurs while handling this event */ void drawText(int x, int y, int letterSpacing, int wordSpacing, - int[] dx, String text) throws IFException; + int[] dx, String text, String ptr) throws IFException; /** * Restricts the current clipping region with the given rectangle. @@ -205,18 +206,20 @@ public interface IFPainter { * an fo:external-graphic in XSL-FO. * @param uri the image's URI * @param rect the rectangle in which the image shall be painted + * @param ptr used for accessibility * @throws IFException if an error occurs while handling this event */ - void drawImage(String uri, Rectangle rect) throws IFException; + void drawImage(String uri, Rectangle rect, String ptr) throws IFException; /** * Draws an image (represented by a DOM document) inside a given rectangle. This is the * equivalent to an fo:instream-foreign-object in XSL-FO. * @param doc the DOM document containing the foreign object * @param rect the rectangle in which the image shall be painted + * @param ptr used for accessibility * @throws IFException if an error occurs while handling this event */ - void drawImage(Document doc, Rectangle rect) throws IFException; + void drawImage(Document doc, Rectangle rect, String ptr) throws IFException; //Note: For now, all foreign objects are handled as DOM documents. At the moment, all known //implementations use a DOM anyway, so optimizing this to work with SAX wouldn't result in //any performance benefits. The IFRenderer itself has a DOM anyway. Only the IFParser could diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java index 92e71d105..47e24445c 100644 --- a/src/java/org/apache/fop/render/intermediate/IFParser.java +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -481,7 +481,8 @@ public class IFParser implements IFConstants { s = lastAttributes.getValue("word-spacing"); int wordSpacing = (s != null ? Integer.parseInt(s) : 0); int[] dx = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx"); - painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString()); + String ptr = lastAttributes.getValue("ptr"); // used for accessibility + painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString(), ptr); } public boolean ignoreCharacters() { @@ -576,9 +577,10 @@ public class IFParser implements IFConstants { int height = Integer.parseInt(lastAttributes.getValue("height")); Map foreignAttributes = getForeignAttributes(lastAttributes); establishForeignAttributes(foreignAttributes); + String ptr = lastAttributes.getValue("ptr"); // used for accessibility if (foreignObject != null) { painter.drawImage(foreignObject, - new Rectangle(x, y, width, height)); + new Rectangle(x, y, width, height), ptr); foreignObject = null; } else { String uri = lastAttributes.getValue( @@ -586,7 +588,7 @@ public class IFParser implements IFConstants { if (uri == null) { throw new IFException("xlink:href is missing on image", null); } - painter.drawImage(uri, new Rectangle(x, y, width, height)); + painter.drawImage(uri, new Rectangle(x, y, width, height), ptr); } resetForeignAttributes(); inForeignObject = false; diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index 558ddfab8..664f1324a 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -818,7 +818,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { currentIPPosition = saveIP; currentBPPosition = saveBP; - currentBPPosition += (int)(bv.getAllocBPD()); + currentBPPosition += (bv.getAllocBPD()); } viewportDimensionStack.pop(); } @@ -886,6 +886,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { // stuff we only need if a link must be created: Rectangle ipRect = null; AbstractAction action = null; + String ptr = (String) ip.getTrait(Trait.PTR); // used for accessibility // make sure the rect is determined *before* calling super! int ipp = currentIPPosition; int bpp = currentBPPosition + ip.getOffset(); @@ -929,6 +930,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { // warn if link trait found but not allowed, else create link if (linkTraitFound) { + action.setPtr(ptr); // used for accessibility Link link = new Link(action, ipRect); this.deferredLinks.add(link); } @@ -963,6 +965,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { String fontName = getInternalFontNameForArea(text); int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); + String ptr = (String)text.getTrait(Trait.PTR); // used for accessibility // This assumes that *all* CIDFonts use a /ToUnicode mapping Typeface tf = getTypeface(fontName); @@ -981,7 +984,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { textUtil.setStartPosition(rx, bl); textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust()); super.renderText(text); - + textUtil.setPtr(ptr); // used for accessibility textUtil.flush(); renderTextDecoration(tf, size, text, bl, rx); } @@ -1054,15 +1057,24 @@ public class IFRenderer extends AbstractPathOrientedRenderer { private static final int INITIAL_BUFFER_SIZE = 16; private int[] dx = new int[INITIAL_BUFFER_SIZE]; private int lastDXPos = 0; - private StringBuffer text = new StringBuffer(); + private final StringBuffer text = new StringBuffer(); private int startx, starty; private int tls, tws; - private boolean combined = false; + private final boolean combined = false; + private String ptr = null; // used for accessibility void addChar(char ch) { text.append(ch); } + /** + * used for accessibility + * @param inPtr to be stored + */ + public void setPtr(String inPtr) { + ptr = inPtr; + } + void adjust(int adjust) { if (adjust != 0) { int idx = text.length(); @@ -1105,9 +1117,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer { System.arraycopy(dx, 0, effDX, 0, size); } if (combined) { - painter.drawText(startx, starty, 0, 0, effDX, text.toString()); + painter.drawText(startx, starty, 0, 0, effDX, text.toString(), ptr); } else { - painter.drawText(startx, starty, tls, tws, effDX, text.toString()); + painter.drawText(startx, starty, tls, tws, effDX, text.toString(), ptr); } } catch (IFException e) { handleIFException(e); @@ -1118,12 +1130,12 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ - public void renderImage(Image image, Rectangle2D pos) { - drawImage(image.getURL(), pos, image.getForeignAttributes()); + public void renderImage(Image image, Rectangle2D pos, String ptr) { + drawImage(image.getURL(), pos, image.getForeignAttributes(), ptr); } /** {@inheritDoc} */ - protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes, String ptr) { Rectangle posInt = new Rectangle( currentIPPosition + (int)pos.getX(), currentBPPosition + (int)pos.getY(), @@ -1132,7 +1144,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { uri = URISpecification.getURL(uri); try { establishForeignAttributes(foreignAttributes); - painter.drawImage(uri, posInt); + painter.drawImage(uri, posInt, ptr); resetForeignAttributes(); } catch (IFException ife) { handleIFException(ife); @@ -1140,7 +1152,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ - public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + public void renderForeignObject(ForeignObject fo, Rectangle2D pos, String ptr) { endTextObject(); Rectangle posInt = new Rectangle( currentIPPosition + (int)pos.getX(), @@ -1150,7 +1162,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { Document doc = fo.getDocument(); try { establishForeignAttributes(fo.getForeignAttributes()); - painter.drawImage(doc, posInt); + painter.drawImage(doc, posInt, ptr); resetForeignAttributes(); } catch (IFException ife) { handleIFException(ife); diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 5076a089f..d7d4a7539 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -25,11 +25,24 @@ import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.Map; +import javax.xml.namespace.NamespaceContext; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @@ -60,10 +73,46 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler implements IFConstants, IFPainter, IFDocumentNavigationHandler { private IFDocumentHandler mimicHandler; + private int pageSequenceCounter; // used for accessibility + private DocumentBuilder parser = null; // used for accessibility + private Document doc = null; // used for accessibility /** Holds the intermediate format state */ private IFState state; + private static class NamespaceContextImpl implements NamespaceContext { + + public String uri; + public String prefix; + + public NamespaceContextImpl() { + } + + public NamespaceContextImpl(String prefix, String uri) { + this.uri = uri; + this.prefix = prefix; + } + + public String getNamespaceURI(String prefix) { + return uri; + } + public void setNamespaceURI(String uri) { + this.uri = uri; + } + + public String getPrefix(String uri) { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + public Iterator getPrefixes(String uri) { + return null; + } + + } + /** * Default constructor. */ @@ -151,8 +200,16 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler handler.startPrefixMapping(DocumentNavigationExtensionConstants.PREFIX, DocumentNavigationExtensionConstants.NAMESPACE); handler.startElement(EL_DOCUMENT); + if (this.getUserAgent().accessibilityEnabled()) { + pageSequenceCounter = 0; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + parser = factory.newDocumentBuilder(); + } } catch (SAXException e) { throw new IFException("SAX error in startDocument()", e); + } catch (ParserConfigurationException pce) { + throw new IFException("Error creating new DocumentBuilder", pce); } } @@ -210,9 +267,33 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (id != null) { atts.addAttribute(XML_NAMESPACE, "id", "xml:id", XMLUtil.CDATA, id); } + handler.startElement(EL_PAGE_SEQUENCE, atts); + if (this.getUserAgent().accessibilityEnabled()) { + if (doc == null) { + doc = parser.parse( + new ByteArrayInputStream(this.getUserAgent().getReducedFOTree())); + } + handler.startElement(EL_STRUCTURE_TREE); // add structure tree + String xpathExpr + = "/fo:root/fo:page-sequence[" + Integer.toString(++pageSequenceCounter) + "]/*"; + XPath xpath = XPathFactory.newInstance().newXPath(); + NamespaceContext namespaceContext + = new NamespaceContextImpl("fo", "http://www.w3.org/1999/XSL/Format"); + xpath.setNamespaceContext(namespaceContext); + NodeList nodes = (NodeList)xpath.evaluate(xpathExpr, doc, XPathConstants.NODESET); + for (int i = 0, n = nodes.getLength(); i < n; i++) { + Node node = nodes.item(i); + new DOM2SAX(handler).writeFragment(node); + } + handler.endElement(EL_STRUCTURE_TREE); + } } catch (SAXException e) { throw new IFException("SAX error in startPageSequence()", e); + } catch (XPathExpressionException e) { + throw new IFException("Error while evaluating XPath expression", e); + } catch (IOException ioe) { + throw new IFException("I/O error while parsing structure tree", ioe); } } @@ -285,6 +366,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler public void startPageTrailer() throws IFException { try { handler.startElement(EL_PAGE_TRAILER); + commitNavigation(); } catch (SAXException e) { throw new IFException("SAX error in startPageTrailer()", e); } @@ -293,7 +375,6 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler /** {@inheritDoc} */ public void endPageTrailer() throws IFException { try { - commitNavigation(); handler.endElement(EL_PAGE_TRAILER); } catch (SAXException e) { throw new IFException("SAX error in endPageTrailer()", e); @@ -382,7 +463,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, XLINK_HREF, uri); @@ -391,6 +472,9 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); + if (ptr != null) { + addAttribute(atts, "ptr", ptr); // used for accessibility + } handler.element(EL_IMAGE, atts); } catch (SAXException e) { throw new IFException("SAX error in startGroup()", e); @@ -409,7 +493,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "x", Integer.toString(rect.x)); @@ -417,6 +501,9 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); + if (ptr != null) { + addAttribute(atts, "ptr", ptr); // used for accessibility + } handler.startElement(EL_IMAGE, atts); new DOM2SAX(handler).writeDocument(doc, true); handler.endElement(EL_IMAGE); @@ -515,7 +602,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler /** {@inheritDoc} */ public void drawText(int x, int y, int letterSpacing, int wordSpacing, - int[] dx, String text) throws IFException { + int[] dx, String text, String ptr) throws IFException { try { AttributesImpl atts = new AttributesImpl(); XMLUtil.addAttribute(atts, XMLConstants.XML_SPACE, "preserve"); @@ -530,6 +617,9 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (dx != null) { addAttribute(atts, "dx", IFUtil.toString(dx)); } + if (ptr != null) { + addAttribute(atts, "ptr", ptr); // used for accessibility + } handler.startElement(EL_TEXT, atts); char[] chars = text.toCharArray(); handler.characters(chars, 0, chars.length); diff --git a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java index f396fd09e..8a4237af1 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java @@ -27,6 +27,7 @@ import org.apache.xmlgraphics.util.XMLizable; public abstract class AbstractAction implements XMLizable { private String id; + private String ptr; // used for accessibility /** * Sets an ID to make the action referencable. @@ -43,7 +44,23 @@ public abstract class AbstractAction implements XMLizable { public String getID() { return this.id; } - + + /** + * Used for accessibility + * @param s representing the ptr + */ + public void setPtr(String s) { + this.ptr = s; + } + + /** + * Used for accessibility + * @return the ptr + */ + public String getPtr() { + return this.ptr; + } + /** * Indicates whether the action has an ID and is therefore referencable. * @return true if the action has an ID diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java index 55c5b8015..da3108973 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DPainter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java @@ -156,7 +156,7 @@ public class Java2DPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { drawImageUsingURI(uri, rect); } @@ -168,7 +168,7 @@ public class Java2DPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { drawImageUsingDocument(doc, rect); } @@ -208,7 +208,7 @@ public class Java2DPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text) + public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text, String ptr) throws IFException { g2dState.updateColor(state.getTextColor()); FontTriplet triplet = new FontTriplet( diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java index 933398125..74c709278 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java +++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java @@ -875,7 +875,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem ImageFlavor.XML_DOM}; /** {@inheritDoc} */ - protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes, String ptr) { int x = currentIPPosition + (int)Math.round(pos.getX()); int y = currentBPPosition + (int)Math.round(pos.getY()); diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java index da4f6a656..57810fb38 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -154,7 +154,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { drawImageUsingURI(uri, rect); } @@ -176,7 +176,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { drawImageUsingDocument(doc, rect); } @@ -312,8 +312,9 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { } /** {@inheritDoc} */ - public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text) + public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text, String ptr) throws IFException { + //Note: ptr is ignored as it is only needed for accessibility try { FontTriplet triplet = new FontTriplet( state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); @@ -474,7 +475,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { Java2DPainter painter = new Java2DPainter(g2d, getContext(), parent.getFontInfo(), state); try { - painter.drawText(x, y, letterSpacing, wordSpacing, dx, text); + painter.drawText(x, y, letterSpacing, wordSpacing, dx, text, ""); } catch (IFException e) { //This should never happen with the Java2DPainter throw new RuntimeException("Unexpected error while painting text", e); diff --git a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java index 841dd7e01..2f4379795 100644 --- a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java +++ b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java @@ -49,4 +49,6 @@ public interface PDFConfigurationConstants { * PDF/X profile is active). */ String KEY_DISABLE_SRGB_COLORSPACE = "disable-srgb-colorspace"; + /** PDF Accessibility */ + String ACCESSIBLITY = "accessibility"; } diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java index 45fb7ec51..750f9a3cf 100644 --- a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -50,6 +50,7 @@ public class PDFContentGenerator { /** the current stream to add PDF commands to */ private PDFStream currentStream; + private boolean accessEnabled; // used for accessibility /** drawing state */ protected PDFPaintingState currentState = null; @@ -63,9 +64,10 @@ public class PDFContentGenerator { * @param document the PDF document * @param out the output stream the PDF document is generated to * @param resourceContext the resource context + * @param accessibilityEnabled indicating if accessibility is enabled or not */ public PDFContentGenerator(PDFDocument document, OutputStream out, - PDFResourceContext resourceContext) { + PDFResourceContext resourceContext, boolean accessibilityEnabled) { this.document = document; this.outputStream = out; this.resourceContext = resourceContext; @@ -78,6 +80,7 @@ public class PDFContentGenerator { }; this.currentState = new PDFPaintingState(); + this.accessEnabled = accessibilityEnabled; } /** @@ -153,6 +156,23 @@ public class PDFContentGenerator { currentStream.add("q\n"); } + /** {@inheritDoc} */ + protected void saveGraphicsState(String structElemType, int sequenceNum) { + endTextObject(); + currentState.save(); + startAccessSequence(structElemType, sequenceNum); + currentStream.add("q\n"); + } + + /** + * Used for accessibility + * @param structElemType Structure Element Type + * @param sequenceNum Sequence number + */ + protected void startAccessSequence(String structElemType, int sequenceNum) { + currentStream.add(structElemType + " <</MCID " + String.valueOf(sequenceNum) + ">>\nBDC\n"); + } + /** * Restored the graphics state valid before the previous {@code #saveGraphicsState()}. * @param popState true if the state should also be popped, false if only the PDF command @@ -171,6 +191,35 @@ public class PDFContentGenerator { restoreGraphicsState(true); } + /** used for accessibility */ + protected void restoreGraphicsStateAccess() { + endTextObject(); + currentStream.add("Q\n"); + currentStream.add("EMC\n"); + currentState.restore(); + } + + /** + * used for accessibility, separates 2 text elements + * @param mcid of new text element + * @param structElemType of parent of new text element + */ + protected void separateTextElements(int mcid, String structElemType) { + textutil.endTextObject(true); + textutil.beginTextObjectAccess(mcid, structElemType); + } + + /** + * used for accessibility + * separates a text element from fo:leader text element + */ + public void separateTextElementFromLeader() { + if (!textutil.inArtifactMode()) { + textutil.endTextObject(true); + textutil.beginArtifactTextObject(); + } + } + /** Indicates the beginning of a text object. */ protected void beginTextObject() { if (!textutil.isInTextObject()) { @@ -178,10 +227,30 @@ public class PDFContentGenerator { } } + /** + * Accessibility beginTextObject + * @param mcid of text element + * @param structElemType of parent + */ + protected void beginTextObjectAccess(int mcid, String structElemType) { + if (!textutil.isInTextObject()) { + textutil.beginTextObjectAccess(mcid, structElemType); + } + } + + /** + * Accessibility begin of LeaderTextObject + */ + public void beginLeaderTextObject() { + if (!textutil.isInTextObject()) { + textutil.beginArtifactTextObject(); + } + } + /** Indicates the end of a text object. */ protected void endTextObject() { if (textutil.isInTextObject()) { - textutil.endTextObject(); + textutil.endTextObject(accessEnabled); } } @@ -326,5 +395,26 @@ public class PDFContentGenerator { restoreGraphicsState(); } + /** + * Places a previously registered image at a certain place on the page. + * Accessibility version + * @param x X coordinate + * @param y Y coordinate + * @param w width for image + * @param h height for image + * @param xobj the image XObject + * @param structElemType of this image + * @param mcid of this image + */ + public void placeImage(float x, float y, float w, float h, PDFXObject xobj, + String structElemType, int mcid) { + saveGraphicsState(structElemType, mcid); + add(format(w) + " 0 0 " + + format(-h) + " " + + format(x) + " " + + format(y + h) + + " cm\n" + xobj.getName() + " Do\n"); + restoreGraphicsStateAccess(); + } } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index b98b15d5e..f7eb3e7b3 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -21,9 +21,28 @@ package org.apache.fop.render.pdf; import java.awt.Dimension; import java.awt.geom.AffineTransform; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import javax.xml.namespace.NamespaceContext; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.xml.sax.SAXException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -32,11 +51,19 @@ import org.apache.xmlgraphics.xmp.Metadata; import org.apache.fop.apps.MimeConstants; import org.apache.fop.fo.extensions.xmp.XMPMetadata; import org.apache.fop.pdf.PDFAnnotList; +import org.apache.fop.pdf.PDFArray; +import org.apache.fop.pdf.PDFDictionary; import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFName; +import org.apache.fop.pdf.PDFNumsArray; +import org.apache.fop.pdf.PDFObject; import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFParentTree; import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; +import org.apache.fop.pdf.PDFStructElem; +import org.apache.fop.pdf.PDFStructTreeRoot; import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; import org.apache.fop.render.intermediate.IFContext; import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; @@ -52,6 +79,70 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** logging instance */ private static Log log = LogFactory.getLog(PDFDocumentHandler.class); + /** the following variables are used for accessibility */ + private int pageSequenceCounter; + private DocumentBuilder parser = null; + private Document doc = null; + private Map structElemType = new HashMap(); + private boolean accessEnabled = false; + private int parentTreeKey = -1; + private int pageLinkCount = 0; + private int mcidKey = -1; + private PDFParentTree parentTree = null; + private Map structTreeMap = new HashMap(); + private List parentTreeList = new java.util.ArrayList(); + + private static class NamespaceContextImpl implements NamespaceContext { + + private String uri; + private String prefix; + + public NamespaceContextImpl() { + } + + public NamespaceContextImpl(String prefix, String uri) { + this.uri = uri; + this.prefix = prefix; + } + + public String getNamespaceURI(String prefix) { + return uri; + } + + public void setNamespaceURI(String uri) { + this.uri = uri; + } + + public String getPrefix(String uri) { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public Iterator getPrefixes(String uri) { + return null; + } + + } + + private static final class ParentTreeEntry { + private final int position; + private final PDFObject object; + private ParentTreeEntry(int p, PDFObject o) { + position = p; + object = o; + } + private int getPosition() { + return position; + } + private PDFObject getPDFObject() { + return object; + } + } + + /** the PDF Document being created */ protected PDFDocument pdfDoc; @@ -79,7 +170,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** Used for bookmarks/outlines. */ protected Map pageReferences = new java.util.HashMap(); - private PDFDocumentNavigationHandler documentNavigationHandler + private final PDFDocumentNavigationHandler documentNavigationHandler = new PDFDocumentNavigationHandler(this); /** @@ -90,7 +181,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public boolean supportsPagesOutOfOrder() { - return true; + return !accessEnabled; } /** {@inheritDoc} */ @@ -123,8 +214,21 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { super.startDocument(); try { this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream); + this.accessEnabled = getUserAgent().accessibilityEnabled(); + if (accessEnabled) { + //TODO: make document language variable, see note on wiki page PDF Accessibility + //TODO: and follow-up emails on fop-dev + this.pdfDoc.getRoot().setLanguage("en"); + parentTree = new PDFParentTree(); + pageSequenceCounter = 0; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + parser = factory.newDocumentBuilder(); + } } catch (IOException e) { throw new IFException("I/O error in startDocument()", e); + } catch (ParserConfigurationException pce) { + throw new IFException("Error creating new DocumentBuilder", pce); } } @@ -137,10 +241,34 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { public void endDocument() throws IFException { try { pdfDoc.getResources().addFonts(pdfDoc, fontInfo); - pdfDoc.outputTrailer(this.outputStream); - + if (getUserAgent().accessibilityEnabled()) { + PDFNumsArray nums = parentTree.getNums(); + for (int i = 0; i <= this.parentTreeKey; i++) { + PDFArray tArray = new PDFArray(); + for (int j = 0; j < parentTreeList.size(); j++) { + if (((ParentTreeEntry)parentTreeList.get(j)).getPosition() == i) { + tArray.add(((ParentTreeEntry)parentTreeList.get(j)).getPDFObject()); + } + } + if (tArray.length() == 1) { + nums.put(i, tArray.get(0)); + } else if (tArray.length() > 1) { + nums.put(i, tArray); + } + } + parentTree.setNums(nums); + getStructTreeRoot().addParentTree(parentTree); + pdfDoc.outputTrailer(this.outputStream); + parser = null; + doc = null; + structElemType = null; + parentTree = null; + structTreeMap = null; + parentTreeList = null; + } else { + pdfDoc.outputTrailer(this.outputStream); + } this.pdfDoc = null; - pdfResources = null; this.generator = null; currentContext = null; @@ -151,9 +279,60 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { super.endDocument(); } + private PDFStructTreeRoot getStructTreeRoot() { + return this.pdfDoc.getRoot().getStructTreeRoot(); + } + + /** {@inheritDoc} */ public void startPageSequence(String id) throws IFException { //TODO page sequence title, country and language + + if (getUserAgent().accessibilityEnabled()) { + try { + if (doc == null) { + doc = parser.parse( + new ByteArrayInputStream(this.getUserAgent().getReducedFOTree())); + } + PDFStructElem parent = (PDFStructElem)getStructTreeRoot().getFirstChild(); + PDFStructElem structElemPart = new PDFStructElem("page-sequence", parent); + this.pdfDoc.assignObjectNumber(structElemPart); + this.pdfDoc.addTrailerObject(structElemPart); + parent.addKid(structElemPart); + + String xpathExpr = "/fo:root/fo:page-sequence[" + + Integer.toString(++pageSequenceCounter) + "]/*"; + XPath xpath = XPathFactory.newInstance().newXPath(); + NamespaceContext namespaceContext = new NamespaceContextImpl("fo", + "http://www.w3.org/1999/XSL/Format"); + xpath.setNamespaceContext(namespaceContext); + + NodeList nodes = (NodeList) xpath.evaluate(xpathExpr, doc, + XPathConstants.NODESET); + + for (int i = 0, n = nodes.getLength(); i < n; i++) { + Node node = nodes.item(i); + if (node.getNodeName().equals("fo:flow") + || node.getNodeName().equals("fo:static-content")) { + PDFStructElem structElemSect = new PDFStructElem( + node.getLocalName(), structElemPart); + this.pdfDoc.assignObjectNumber(structElemSect); + this.pdfDoc.addTrailerObject(structElemSect); + structElemPart.addKid(structElemSect); + NodeList iNodes = node.getChildNodes(); + for (int j = 0, m = iNodes.getLength(); j < m; j++) { + processContent(iNodes.item(j), structElemSect, 1); + } + } + } + } catch (SAXException e) { + throw new IFException("SAX error in startPageSequence()", e); + } catch (XPathExpressionException e) { + throw new IFException("Error while evaluating XPath expression", e); + } catch (IOException ioe) { + throw new IFException("I/O error while parsing structure tree", ioe); + } + } } /** {@inheritDoc} */ @@ -164,22 +343,25 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void startPage(int index, String name, String pageMasterName, Dimension size) throws IFException { + // used for accessibility + this.parentTreeKey = this.parentTreeKey + this.pageLinkCount + 1; + this.mcidKey = 0; + this.pageLinkCount = 0; + // this.pdfResources = this.pdfDoc.getResources(); this.currentPage = this.pdfDoc.getFactory().makePage( this.pdfResources, (int)Math.round(size.getWidth() / 1000), - (int)Math.round(size.getHeight() / 1000), - index); - //pageReferences.put(new Integer(index)/*page.getKey()*/, currentPage.referencePDF()); - //pvReferences.put(page.getKey(), page); - + (int)Math.round(size.getHeight() / 1000), index, + parentTreeKey); // used for accessibility pdfUtil.generatePageLabel(index, name); currentPageRef = new PageReference(currentPage, size); this.pageReferences.put(new Integer(index), currentPageRef); - this.generator = new PDFContentGenerator(this.pdfDoc, this.outputStream, this.currentPage); + this.generator = new PDFContentGenerator(this.pdfDoc, this.outputStream, + this.currentPage, this.accessEnabled); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFPainter's AffineTransform basicPageTransform = new AffineTransform(1, 0, 0, -1, 0, size.height / 1000f); @@ -234,8 +416,8 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { static final class PageReference { - private PDFReference pageRef; - private Dimension pageDimension; + private final PDFReference pageRef; + private final Dimension pageDimension; private PageReference(PDFPage page, Dimension dim) { this.pageRef = page.makeReference(); @@ -251,4 +433,191 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { } } + /** + * Used for accessibility + * @param position in parentTree + * @param o reference of PDFObject to be added to parentTree + */ + void addToParentTree(int position, PDFObject o) { + PDFNumsArray nums = parentTree.getNums(); + nums.put(position, o); + parentTree.setNums(nums); + } + + + /** + * Used for accessibility + * @param position in parentTree + * @param o object to be added to parentTree + */ + void addToTempList(int position, PDFObject o) { + ParentTreeEntry myEntry = new ParentTreeEntry(position, o); + this.parentTreeList.add(myEntry); + } + + + /** + * Return the PDFObject + * @param ptr this is the key + * @return PDFObject referenced with ptr + */ + PDFObject getTrailerObject(String ptr) { + return (PDFObject) this.structTreeMap.get(ptr); + } + + /** + * Return the parent PDFObject referenced by ptr + * @param ptr this is the key + * @return PDFObject parent of PDFObject referenced with ptr + */ + PDFObject getParentTrailerObject(String ptr) { + PDFStructElem tempStructElem = (PDFStructElem) this.structTreeMap.get(ptr); + return tempStructElem.getParentStructElem(); + } + + /** + * Adds a link object as child to StructElem + * @param ptr of PDFStructElem + * @param o PDFLink object + */ + void addLinkToStructElem(String ptr, PDFObject o) { + PDFDictionary dict = new PDFDictionary(); + dict.put("Type", new PDFName("OBJR")); + dict.put("Pg", this.currentPage); + dict.put("Obj", o); + PDFStructElem tempStructElem = (PDFStructElem) structTreeMap.get(ptr); + tempStructElem.addKid(dict); + } + + /** + * Adds a child to StructElem, called from PDFPainter.drawImage + * @param ptr of PDFStructElem + * @param mcid sequence number within page + */ + void addChildToStructElemImage(String ptr, int mcid) { + PDFStructElem tempStructElem = (PDFStructElem) structTreeMap.get(ptr); + tempStructElem.addMCIDKid(mcid); + tempStructElem.addPage(this.currentPage); + if (!tempStructElem.getLevel1()) { + addMeToParent(tempStructElem); + } + } + + /** + * Adds a child to StructElem, called from PDFPainter.drawText + * @param ptr of PDFSturctElem + * @param mcid sequence number within page + */ + void addChildToStructElemText(String ptr, int mcid) { + PDFDictionary dict = new PDFDictionary(); + dict.put("Type", new PDFName("MCR")); + dict.put("Pg", this.currentPage); + dict.put("MCID", mcid); + PDFStructElem tempStructElem = (PDFStructElem) structTreeMap.get(ptr); + tempStructElem.addKid(dict); + if (!tempStructElem.getLevel1()) { + addMeToParent(tempStructElem); + } + } + + /** + * Add child PDFStructElem to parent child elements + * Repeat until level 1 or child already exists + * @param childStructElem to be added + */ + protected void addMeToParent(PDFStructElem childStructElem) { + PDFStructElem parentStructElem = (PDFStructElem) childStructElem.getParentStructElem(); + // test if child already exists or not + if (parentStructElem.addUniqueKid(childStructElem)) { + if (!parentStructElem.getLevel1()) { + addMeToParent(parentStructElem); + } + } + } + + /** + * increment MCID value + */ + void incMCID() { + this.mcidKey++; + } + + /** + * MCID is a sequential number per page + * @return MCID value + */ + int getMCID() { + return this.mcidKey; + } + + /** + * Used for accessibility + * @param ptr pointer into map of all structElems + * @return type of found structElem + */ + String getStructElemType(String ptr) { + return (String) structElemType.get(ptr); + } + + /** + * Used for accessibility + * @param me node being processed + * @param parent parent node in DOM of me + * @param depth depth level in DOM, static-content & flow are 0 + */ + private void processContent(Node me, PDFStructElem parent, int depth) { + String ptr; + Node attr = me.getAttributes().getNamedItem("foi:ptr"); + if (attr != null) { + ptr = attr.getNodeValue(); + } else { + log.error("Accessibility: missing foi:ptr"); + ptr = ""; + } + String s = me.getLocalName(); + PDFStructElem structElem = new PDFStructElem(s, parent); + this.pdfDoc.assignObjectNumber(structElem); + this.pdfDoc.addTrailerObject(structElem); + if (depth == 1) { + parent.addKid(structElem); + structElem.setLevel1(); + } + if (s.equals("external-graphic") || s.equals("instream-foreign-object")) { + Node altTextNode = me.getAttributes().getNamedItem("fox:alt-text"); + if (altTextNode != null) { + structElem.put("Alt", altTextNode.getNodeValue()); + } else { + log.warn("fo:" + s + + " requires an alternative text attribute fox:alt-text for accessibility"); + structElem.put("Alt", "No alternate text specified"); + } + } + // the following map is used e.g. in PDFPainter.drawText + structElemType.put(ptr, structElem.get("S").toString()); + // this map will be used for fast access of the StructElem by ptr + structTreeMap.put(ptr, structElem); + NodeList nodes = me.getChildNodes(); + depth++; + for (int i = 0, n = nodes.getLength(); i < n; i++) { + processContent(nodes.item(i), structElem, depth); + } + } + + /** + * used for accessibility + * @return mcid to be used for next link to be processed + */ + int getPageLinkCountPlusPageParentKey() { + this.pageLinkCount++; + return (this.parentTreeKey + this.pageLinkCount); + } + + /** + * used for accessibility + * @return current parentTreeKey + */ + int getCurrentParentTreeKey() { + return this.parentTreeKey; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java index bc22bb6d2..2f2de2e62 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java @@ -42,15 +42,18 @@ import org.apache.fop.render.intermediate.extensions.NamedDestination; import org.apache.fop.render.intermediate.extensions.URIAction; import org.apache.fop.render.pdf.PDFDocumentHandler.PageReference; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + /** * Implementation of the {@link IFDocumentNavigationHandler} interface for PDF output. */ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler { + private static Log log = LogFactory.getLog(PDFDocumentHandler.class); + private final PDFDocumentHandler documentHandler; - private PDFDocumentHandler documentHandler; - - private Map incompleteActions = new java.util.HashMap(); - private Map completeActions = new java.util.HashMap(); + private final Map incompleteActions = new java.util.HashMap(); + private final Map completeActions = new java.util.HashMap(); /** * Default constructor. @@ -111,6 +114,14 @@ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler PDFLink pdfLink = getPDFDoc().getFactory().makeLink( targetRect2D, pdfAction); if (pdfLink != null) { + //accessibility: ptr has a value + String ptr = link.getAction().getPtr(); + if (ptr.length() > 0) { + this.documentHandler.addLinkToStructElem(ptr, pdfLink); + int id = this.documentHandler.getPageLinkCountPlusPageParentKey(); + pdfLink.setStructParent(id); + this.documentHandler.addToParentTree(id, pdfLink ); + } documentHandler.currentPage.addAnnotation(pdfLink); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java index d47d5a439..074a19bec 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java @@ -82,7 +82,17 @@ public class PDFImageHandlerRawJPEG implements PDFImageHandler, ImageHandler { float y = (float)pos.getY() / 1000f; float w = (float)pos.getWidth() / 1000f; float h = (float)pos.getHeight() / 1000f; - generator.placeImage(x, y, w, h, xobj); + if (context.getUserAgent().accessibilityEnabled()) { + String structElemType = pdfContext.getStructElemType(); + if (structElemType.length() > 0) { + int sequenceNum = pdfContext.getSequenceNum(); + generator.placeImage(x, y, w, h, xobj, structElemType, sequenceNum); + } else { + generator.placeImage(x, y, w, h, xobj); + } + } else { + generator.placeImage(x, y, w, h, xobj); + } } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java index 3e57c7216..728fa0601 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java @@ -83,7 +83,13 @@ public class PDFImageHandlerRenderedImage implements PDFImageHandler, ImageHandl float y = (float)pos.getY() / 1000f; float w = (float)pos.getWidth() / 1000f; float h = (float)pos.getHeight() / 1000f; - generator.placeImage(x, y, w, h, xobj); + if (context.getUserAgent().accessibilityEnabled()) { + String structElemType = pdfContext.getStructElemType(); + int sequenceNum = pdfContext.getSequenceNum(); + generator.placeImage(x, y, w, h, xobj, structElemType, sequenceNum); + } else { + generator.placeImage(x, y, w, h, xobj); + } } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java index d1b7aa986..40fabbc5b 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java @@ -101,8 +101,8 @@ public class PDFImageHandlerSVG implements ImageHandler { float w = (float)ctx.getDocumentSize().getWidth() * 1000f; float h = (float)ctx.getDocumentSize().getHeight() * 1000f; - float sx = pos.width / (float)w; - float sy = pos.height / (float)h; + float sx = pos.width / w; + float sy = pos.height / h; //Scaling and translation for the bounding box of the image AffineTransform scaling = new AffineTransform( @@ -121,6 +121,11 @@ public class PDFImageHandlerSVG implements ImageHandler { */ generator.comment("SVG setup"); generator.saveGraphicsState(); + if (context.getUserAgent().accessibilityEnabled()) { + String structElemType = pdfContext.getStructElemType(); + int sequenceNum = pdfContext.getSequenceNum(); + generator.startAccessSequence(structElemType, sequenceNum); + } generator.setColor(Color.black, false); generator.setColor(Color.black, true); @@ -168,7 +173,11 @@ public class PDFImageHandlerSVG implements ImageHandler { eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI()); } generator.getState().restore(); - generator.restoreGraphicsState(); + if (context.getUserAgent().accessibilityEnabled()) { + generator.restoreGraphicsStateAccess(); + } else { + generator.restoreGraphicsState(); + } generator.comment("SVG end"); } diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index fa00fd7b4..b87569ed3 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -59,12 +59,18 @@ public class PDFPainter extends AbstractIFPainter { /** logging instance */ private static Log log = LogFactory.getLog(PDFPainter.class); - private PDFDocumentHandler documentHandler; + private final PDFDocumentHandler documentHandler; /** The current content generator */ protected PDFContentGenerator generator; - private PDFBorderPainter borderPainter; + private final PDFBorderPainter borderPainter; + + private boolean accessEnabled = false; + + private int mcid; // used for accessibility + + private String structElemType; // used for accessibility /** * Default constructor. @@ -76,6 +82,7 @@ public class PDFPainter extends AbstractIFPainter { this.generator = documentHandler.generator; this.borderPainter = new PDFBorderPainter(this.generator); this.state = IFState.create(); + accessEnabled = this.getUserAgent().accessibilityEnabled(); } /** {@inheritDoc} */ @@ -122,15 +129,50 @@ public class PDFPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) + throws IFException { PDFXObject xobject = getPDFDoc().getXObject(uri); if (xobject != null) { - placeImage(rect, xobject); + if (accessEnabled && ptr.length() > 0) { + mcid = this.documentHandler.getMCID(); + mcid++; // fix for Acro Checker + this.documentHandler.incMCID(); // simulating a parent text element + structElemType = this.documentHandler.getStructElemType(ptr); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getParentTrailerObject(ptr)); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + placeImageAccess(rect, xobject); + this.documentHandler.addChildToStructElemImage(ptr, mcid); + this.documentHandler.incMCID(); + } else { + placeImage(rect, xobject); + } return; } - - drawImageUsingURI(uri, rect); - + if (accessEnabled && ptr.length() > 0) { + mcid = this.documentHandler.getMCID(); + mcid++; // fix for Acro Checker + this.documentHandler.incMCID(); // simulating a parent text element + structElemType = this.documentHandler.getStructElemType(ptr); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getParentTrailerObject(ptr)); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + //PDFRenderingContext pdfContext = new PDFRenderingContext( + // getUserAgent(), generator, this.documentHandler.currentPage, getFontInfo()); + //pdfContext.setMCID(mcid); + //pdfContext.setStructElemType(structElemType); + drawImageUsingURI(uri, rect); + this.documentHandler.addChildToStructElemImage(ptr, mcid); + this.documentHandler.incMCID(); + } else { + drawImageUsingURI(uri, rect); + } flushPDFDoc(); } @@ -138,6 +180,8 @@ public class PDFPainter extends AbstractIFPainter { protected RenderingContext createRenderingContext() { PDFRenderingContext pdfContext = new PDFRenderingContext( getUserAgent(), generator, this.documentHandler.currentPage, getFontInfo()); + pdfContext.setMCID(mcid); + pdfContext.setStructElemType(structElemType); return pdfContext; } @@ -158,11 +202,43 @@ public class PDFPainter extends AbstractIFPainter { + " cm " + xobj.getName() + " Do\n"); generator.restoreGraphicsState(); } + /** + * Places a previously registered image at a certain place on the page - Accessibility version + * @param x X coordinate + * @param y Y coordinate + * @param w width for image + * @param h height for image + * @param xobj the image XObject + */ + private void placeImageAccess(Rectangle rect, PDFXObject xobj) { + generator.saveGraphicsState(structElemType, mcid); + generator.add(format(rect.width) + " 0 0 " + + format(-rect.height) + " " + + format(rect.x) + " " + + format(rect.y + rect.height ) + + " cm " + xobj.getName() + " Do\n"); + generator.restoreGraphicsStateAccess(); + } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { - drawImageUsingDocument(doc, rect); - + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { + if (accessEnabled && ptr.length() > 0) { + mcid = this.documentHandler.getMCID(); + mcid++; // fix for Acro Checker + this.documentHandler.incMCID(); // simulating a parent text element + structElemType = this.documentHandler.getStructElemType(ptr); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getParentTrailerObject(ptr)); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + drawImageUsingDocument(doc, rect); + this.documentHandler.addChildToStructElemImage(ptr, mcid); + this.documentHandler.incMCID(); + } else { + drawImageUsingDocument(doc, rect); + } flushPDFDoc(); } @@ -253,10 +329,38 @@ public class PDFPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text) + public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, + String text, String ptr) throws IFException { - generator.updateColor(state.getTextColor(), true, null); - generator.beginTextObject(); + if (accessEnabled ) { + int mcId; + String structElType = ""; + if (ptr != null && ptr.length() > 0) { + mcId = this.documentHandler.getMCID(); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + structElType = this.documentHandler.getStructElemType(ptr); + if (generator.getTextUtil().isInTextObject()) { + generator.separateTextElements(mcId, structElType); + } + generator.updateColor(state.getTextColor(), true, null); + generator.beginTextObjectAccess(mcId, structElType); + this.documentHandler.addChildToStructElemText(ptr, mcId); + this.documentHandler.incMCID(); + } else { + // <fo:leader leader-pattern="use-content"> + if (generator.getTextUtil().isInTextObject()) { + generator.separateTextElementFromLeader(); + } + generator.updateColor(state.getTextColor(), true, null); + generator.beginLeaderTextObject(); + } + } else { + generator.updateColor(state.getTextColor(), true, null); + generator.beginTextObject(); + } + FontTriplet triplet = new FontTriplet( state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); //TODO Ignored: state.getFontVariant() @@ -277,7 +381,7 @@ public class PDFPainter extends AbstractIFPainter { PDFTextUtil textutil = generator.getTextUtil(); textutil.updateTf(fontKey, fontSize, tf.isMultiByte()); - generator.updateCharacterSpacing((float)letterSpacing / 1000f); + generator.updateCharacterSpacing(letterSpacing / 1000f); textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, x / 1000f, y / 1000f)); int l = text.length(); diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index c40c94fc4..c6b4d4977 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -461,7 +461,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf double h = bounds.getHeight(); pageHeight = (int) h; - this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage); + this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage, + false); this.borderPainter = new PDFBorderPainter(this.generator); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFRenderer's @@ -1073,7 +1074,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf } /** {@inheritDoc} */ - protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes, String ptr) { endTextObject(); putImage(url, pos, foreignAttributes); } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java index 98b0c8203..f82019b4a 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java @@ -34,6 +34,11 @@ public class PDFRenderingContext extends AbstractRenderingContext { private PDFContentGenerator generator; private FontInfo fontInfo; private PDFPage page; + /** Temp. val. for accessibility, used in PDFImageHandlerRenderedImage */ + private String structElemType = ""; + + /** Temp. val. for accessibility, used in PDFImageHandlerRenderedImage */ + private int mcid = -1; /** * Main constructor. @@ -79,4 +84,35 @@ public class PDFRenderingContext extends AbstractRenderingContext { return this.fontInfo; } + /** + * Used for accessibility, used in PDFPainter.drawImage + * @param value to be stored + */ + public void setMCID(int value) { + mcid = value; + } + + /** + * Used for accessibility + * @return mcid + */ + public int getSequenceNum() { + return mcid; + } + + /** + * Used for accessibility + * @param s the type of the structure element + */ + public void setStructElemType(String s) { + structElemType = s; + } + + /** + * Used for accessibility + * @return the type of the structure element + */ + public String getStructElemType() { + return structElemType; + } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index 2e3c83126..8d9536bff 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -52,6 +52,8 @@ import org.apache.fop.pdf.PDFMetadata; import org.apache.fop.pdf.PDFNumsArray; import org.apache.fop.pdf.PDFOutputIntent; import org.apache.fop.pdf.PDFPageLabels; +import org.apache.fop.pdf.PDFStructElem; +import org.apache.fop.pdf.PDFStructTreeRoot; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.util.ColorProfileUtil; @@ -75,6 +77,9 @@ class PDFRenderingUtil implements PDFConfigurationConstants { /** the PDF/X mode (Default: disabled) */ protected PDFXMode pdfXMode = PDFXMode.DISABLED; + /** the accessibility mode (Default: false=disabled) */ + protected boolean accessibility = false; + /** the (optional) encryption parameters */ protected PDFEncryptionParams encryptionParams; @@ -169,6 +174,12 @@ class PDFRenderingUtil implements PDFConfigurationConstants { if (s != null) { this.outputProfileURI = s; } + // used for accessibility + setting = userAgent.getRendererOptions().get(ACCESSIBLITY); + if (setting != null) { + this.accessibility = booleanValueOf(setting); + } + setting = userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE); if (setting != null) { this.disableSRGBColorSpace = booleanValueOf(setting); @@ -384,6 +395,16 @@ class PDFRenderingUtil implements PDFConfigurationConstants { log.debug("PDF/A is active. Conformance Level: " + pdfAMode); addPDFA1OutputIntent(); } + if (this.accessibility) { + this.pdfDoc.getRoot().makeTagged(); + log.info("Accessibility is enabled"); + PDFStructTreeRoot structTreeRoot = this.pdfDoc.getFactory().makeStructTreeRoot(); + this.pdfDoc.getRoot().setStructTreeRoot(structTreeRoot); + PDFStructElem structElemDocument = new PDFStructElem("root", structTreeRoot); + this.pdfDoc.assignObjectNumber(structElemDocument); + this.pdfDoc.addTrailerObject(structElemDocument); + structTreeRoot.addKid(structElemDocument); + } return this.pdfDoc; } diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index cb88f4670..807bf50b4 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -176,7 +176,7 @@ public class PSPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { try { endTextObject(); } catch (IOException ioe) { @@ -186,7 +186,7 @@ public class PSPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { try { endTextObject(); } catch (IOException ioe) { @@ -338,7 +338,7 @@ public class PSPainter extends AbstractIFPainter { /** {@inheritDoc} */ public void drawText(int x, int y, int letterSpacing, int wordSpacing, - int[] dx, String text) throws IFException { + int[] dx, String text, String ptr) throws IFException { try { //Note: dy is currently ignored PSGenerator generator = getGenerator(); diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index 19fcd8af8..5b918156b 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -345,7 +345,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer } /** {@inheritDoc} */ - protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes, String ptr) { endTextObject(); int x = currentIPPosition + (int)Math.round(pos.getX()); int y = currentBPPosition + (int)Math.round(pos.getY()); @@ -1233,7 +1233,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer * {@inheritDoc} */ public void renderImage(Image image, Rectangle2D pos) { - drawImage(image.getURL(), pos, image.getForeignAttributes()); + drawImage(image.getURL(), pos, image.getForeignAttributes(), ""); } /** diff --git a/src/java/org/apache/fop/render/txt/TXTRenderer.java b/src/java/org/apache/fop/render/txt/TXTRenderer.java index 575f1232f..2170a67d2 100644 --- a/src/java/org/apache/fop/render/txt/TXTRenderer.java +++ b/src/java/org/apache/fop/render/txt/TXTRenderer.java @@ -443,7 +443,7 @@ public class TXTRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ - protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes, String ptr) { //No images are painted here } diff --git a/src/java/org/apache/fop/util/DOM2SAX.java b/src/java/org/apache/fop/util/DOM2SAX.java index 839cf107f..b9021ed3a 100644 --- a/src/java/org/apache/fop/util/DOM2SAX.java +++ b/src/java/org/apache/fop/util/DOM2SAX.java @@ -77,6 +77,15 @@ public class DOM2SAX { contentHandler.endDocument(); } } + + /** + * Writes the given fragment using the given ContentHandler. + * @param node DOM node + * @throws SAXException In case of a problem while writing XML + */ + public void writeFragment(Node node) throws SAXException { + writeNode(node); + } /** * Begin the scope of namespace prefix. Forward the event to the SAX handler diff --git a/src/sandbox/org/apache/fop/render/svg/SVGPainter.java b/src/sandbox/org/apache/fop/render/svg/SVGPainter.java index 0ee13c021..cb0547391 100644 --- a/src/sandbox/org/apache/fop/render/svg/SVGPainter.java +++ b/src/sandbox/org/apache/fop/render/svg/SVGPainter.java @@ -193,7 +193,7 @@ public class SVGPainter extends AbstractIFPainter implements SVGConstants { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { try { establish(MODE_NORMAL); @@ -243,7 +243,7 @@ public class SVGPainter extends AbstractIFPainter implements SVGConstants { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { try { establish(MODE_NORMAL); @@ -325,8 +325,10 @@ public class SVGPainter extends AbstractIFPainter implements SVGConstants { } /** {@inheritDoc} */ - public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, String text) - throws IFException { + + public void drawText(int x, int y, int letterSpacing, int wordSpacing, int[] dx, + String text, String ptr) throws IFException { + //Note: ptr is ignored as it is only needed for accessibility try { establish(MODE_TEXT); AttributesImpl atts = new AttributesImpl(); |