diff options
Diffstat (limited to 'src/java/org')
71 files changed, 2490 insertions, 89 deletions
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..4063d3f9f --- /dev/null +++ b/src/java/org/apache/fop/accessibility/AccessibilityUtil.java @@ -0,0 +1,100 @@ +/* + * 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 { + + /** Constant string for the rendering options key to enable accessibility features. */ + public static final String ACCESSIBILITY = "accessibility"; + + private static SAXTransformerFactory tfactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + private static Templates addPtrTemplates; + private static Templates reduceFOTemplates; + + /** + * Decorates the given {@link DefaultHandler} so the structure tree used for accessibility + * features can be branched off the main content stream. + * @param handler the handler to decorate + * @param userAgent the user agent + * @return the decorated handler + * @throws FOPException if an error occurs setting up the decoration + */ + 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..b127a74b5 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/TransformerNode.java @@ -0,0 +1,216 @@ +/* + * 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.Result; +import javax.xml.transform.Templates; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; + +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; + + /** + * 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) { + transformerHandler.setDocumentLocator(locator); + } + + /** {@inheritDoc} */ + public void startDocument() throws SAXException { + transformerHandler.startDocument(); + } + + /** {@inheritDoc} */ + public void endDocument() throws SAXException { + transformerHandler.endDocument(); + } + + /** {@inheritDoc} */ + public void processingInstruction(String target, String data) throws SAXException { + transformerHandler.processingInstruction(target, data); + } + + /** {@inheritDoc} */ + public void startElement(String uri, String local, String raw, Attributes attrs) + throws SAXException { + AttributesImpl ai = new AttributesImpl(attrs); + transformerHandler.startElement(uri, local, raw, ai); + } + + /** {@inheritDoc} */ + public void characters(char[] ch, int start, int length) throws SAXException { + transformerHandler.characters(ch, start, length); + } + + /** {@inheritDoc} */ + public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { + transformerHandler.ignorableWhitespace(ch, start, length); + } + + /** {@inheritDoc} */ + public void endElement(String uri, String local, String raw) throws SAXException { + transformerHandler.endElement(uri, local, raw); + } + + /** {@inheritDoc} */ + public void skippedEntity(String string) throws SAXException { + transformerHandler.skippedEntity(string); + } + + /** {@inheritDoc} */ + public void startPrefixMapping(String string, String string1) throws SAXException { + transformerHandler.startPrefixMapping(string, string1); + } + + /** {@inheritDoc} */ + public void endPrefixMapping(String string) throws SAXException { + 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 { + transformerHandler.startDTD(name, pid, lid); + } + + /** + * End of DTD + * + * @throws SAXException + * - if parser fails + */ + public void endDTD() throws SAXException { + transformerHandler.endDTD(); + } + + /** + * startEnitity. + * + * @param string + * - param 1 + * @throws SAXException + * - if parser fails + */ + public void startEntity(String string) throws SAXException { + transformerHandler.startEntity(string); + } + + /** + * end Entity + * + * @param string + * - param 1 + * @throws SAXException + * - if paser fails + */ + public void endEntity(String string) throws SAXException { + transformerHandler.endEntity(string); + } + + /** + * Start of CDATA section + * + * @throws SAXException + * - parser fails + */ + public void startCDATA() throws SAXException { + transformerHandler.startCDATA(); + } + + /** + * endCDATA section + * + * @throws SAXException + * - if paser fails + */ + public void endCDATA() throws SAXException { + 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 { + 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..34974233a --- /dev/null +++ b/src/java/org/apache/fop/accessibility/TransformerNodeEndProcessing.java @@ -0,0 +1,97 @@ +/* + * 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.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.commons.io.output.ByteArrayOutputStream; + +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); + } + + /** {@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..84c500639 --- /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> + + <!-- the following nodes are being ignored/filtered --> + + <xsl:template match="text()"/> + + <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 8ee472dcf..13db8d5ef 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -37,6 +37,7 @@ import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.image.loader.impl.AbstractImageSessionContext; import org.apache.fop.Version; +import org.apache.fop.accessibility.AccessibilityUtil; import org.apache.fop.events.DefaultEventBroadcaster; import org.apache.fop.events.Event; import org.apache.fop.events.EventBroadcaster; @@ -99,6 +100,9 @@ public class FOUserAgent { private boolean conserveMemoryPolicy = false; 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.) */ @@ -152,6 +156,9 @@ public class FOUserAgent { this.factory = factory; setBaseURL(factory.getBaseURL()); setTargetResolution(factory.getTargetResolution()); + if (this.getRendererOptions().get(AccessibilityUtil.ACCESSIBILITY) == null) { + this.rendererOptions.put(AccessibilityUtil.ACCESSIBILITY, Boolean.FALSE); + } } /** @return the associated FopFactory instance */ @@ -196,6 +203,7 @@ public class FOUserAgent { return rendererOverride; } + /** * Sets an explicit FOEventHandler instance which overrides the one * defined by the render type setting. @@ -352,7 +360,7 @@ public class FOUserAgent { getFactory().getFontManager().setFontBaseURL(fontBaseUrl); } catch (MalformedURLException e) { throw new IllegalArgumentException(e.getMessage()); - } + } } /** @@ -642,5 +650,34 @@ public class FOUserAgent { this.conserveMemoryPolicy = conserveMemoryPolicy; } + /** + * Check if accessibility is enabled. + * @return true if accessibility is enabled + */ + public boolean isAccessibilityEnabled() { + Boolean enabled = (Boolean)this.getRendererOptions().get(AccessibilityUtil.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..9dfa70325 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.isAccessibilityEnabled()) { + 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 9ddc164c5..6693f33c4 100644 --- a/src/java/org/apache/fop/apps/FopFactory.java +++ b/src/java/org/apache/fop/apps/FopFactory.java @@ -41,6 +41,7 @@ import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.image.loader.ImageContext; import org.apache.xmlgraphics.image.loader.ImageManager; +import org.apache.fop.accessibility.AccessibilityUtil; import org.apache.fop.fo.ElementMapping; import org.apache.fop.fo.ElementMappingRegistry; import org.apache.fop.fonts.FontCache; @@ -100,6 +101,11 @@ public class FopFactory implements ImageContext { */ 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 +187,20 @@ public class FopFactory implements ImageContext { */ public FOUserAgent newFOUserAgent() { FOUserAgent userAgent = new FOUserAgent(this); + userAgent.getRendererOptions().put(AccessibilityUtil.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 2beb5373c..a8b964a5d 100644 --- a/src/java/org/apache/fop/apps/FopFactoryConfigurator.java +++ b/src/java/org/apache/fop/apps/FopFactoryConfigurator.java @@ -92,6 +92,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/AreaTreeParser.java b/src/java/org/apache/fop/area/AreaTreeParser.java index aae9b5017..25d110086 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -84,6 +84,7 @@ import org.apache.fop.util.ContentHandlerFactory; import org.apache.fop.util.ContentHandlerFactoryRegistry; import org.apache.fop.util.ConversionUtils; import org.apache.fop.util.DefaultErrorListener; +import org.apache.fop.util.XMLConstants; import org.apache.fop.util.XMLUtil; /** @@ -991,10 +992,10 @@ public class AreaTreeParser { ExtensionAttachment attachment = (ExtensionAttachment)obj; ato.addExtensionAttachment(attachment); } else { - log.warn("Don't know how to handle externally generated object: " + obj); - } + log.warn("Don't know how to handle externally generated object: " + obj); } } + } private void setAreaAttributes(Attributes attributes, Area area) { area.setIPD(Integer.parseInt(attributes.getValue("ipd"))); @@ -1133,7 +1134,7 @@ public class AreaTreeParser { for (int i = 0, c = atts.getLength(); i < c; i++) { String ns = atts.getURI(i); if (ns.length() > 0) { - if ("http://www.w3.org/2000/xmlns/".equals(ns)) { + if (XMLConstants.XMLNS_NAMESPACE_URI.equals(ns)) { continue; } QName qname = new QName(ns, atts.getQName(i)); 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 7a6bc7353..ece99d1de 100644 --- a/src/java/org/apache/fop/cli/CommandLineOptions.java +++ b/src/java/org/apache/fop/cli/CommandLineOptions.java @@ -35,6 +35,7 @@ import org.apache.commons.logging.LogFactory; import org.xml.sax.SAXException; import org.apache.fop.Version; +import org.apache.fop.accessibility.AccessibilityUtil; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FopFactory; @@ -331,6 +332,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(AccessibilityUtil.ACCESSIBILITY, Boolean.TRUE); } else if (args[i].equals("-v")) { /* Currently just print the version */ printVersion(); @@ -1137,6 +1140,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" + " -conserve Enable memory-conservation policy (trades memory-consumption for disk I/O)\n" diff --git a/src/java/org/apache/fop/fo/Constants.java b/src/java/org/apache/fop/fo/Constants.java index aff13a8bf..5f23502f3 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 debb6a4b1..67f5d1d30 100644 --- a/src/java/org/apache/fop/fo/FONode.java +++ b/src/java/org/apache/fop/fo/FONode.java @@ -35,6 +35,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; @@ -414,6 +415,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..0096f881f 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-text, 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..bd4152295 100644 --- a/src/java/org/apache/fop/fo/flow/AbstractGraphics.java +++ b/src/java/org/apache/fop/fo/flow/AbstractGraphics.java @@ -29,6 +29,7 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.LengthRangeProperty; import org.apache.fop.fo.properties.SpaceProperty; +import org.apache.fop.fo.properties.StructurePointerPropertySet; /** * Common base class for the <a href="http://www.w3.org/TR/xsl/#fo_instream-foreign-object"> @@ -36,7 +37,8 @@ import org.apache.fop.fo.properties.SpaceProperty; * and <a href="http://www.w3.org/TR/xsl/#fo_external-graphic"> * <code>fo:external-graphic</code></a> flow formatting objects. */ -public abstract class AbstractGraphics extends FObj implements GraphicsProperties { +public abstract class AbstractGraphics extends FObj + implements GraphicsProperties, StructurePointerPropertySet { // The value of properties relevant for fo:instream-foreign-object // and external-graphics. @@ -60,6 +62,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 +97,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 +211,11 @@ public abstract class AbstractGraphics extends FObj implements GraphicsPropertie return keepWithPrevious; } + /** {@inheritDoc} */ + 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..0f4575c5b 100644 --- a/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java +++ b/src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java @@ -21,8 +21,8 @@ package org.apache.fop.fo.flow; import java.awt.Color; -import org.xml.sax.Locator; import org.xml.sax.Attributes; +import org.xml.sax.Locator; import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; @@ -35,6 +35,7 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.CommonFont; import org.apache.fop.fo.properties.CommonTextDecoration; import org.apache.fop.fo.properties.SpaceProperty; +import org.apache.fop.fo.properties.StructurePointerPropertySet; /** * Common base class for the <a href="http://www.w3.org/TR/xsl/#fo_page-number-citation"> @@ -42,7 +43,8 @@ import org.apache.fop.fo.properties.SpaceProperty; * <a href="http://www.w3.org/TR/xsl/#fo_page-number-citation-last"> * <code>fo:page-number-citation-last</code></a> objects. */ -public abstract class AbstractPageNumberCitation extends FObj { +public abstract class AbstractPageNumberCitation extends FObj + implements StructurePointerPropertySet { // The value of properties relevant for fo:page-number-citation(-last). private CommonBorderPaddingBackground commonBorderPaddingBackground; @@ -51,6 +53,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 +99,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 +142,11 @@ public abstract class AbstractPageNumberCitation extends FObj { return textDecoration; } + /** {@inheritDoc} */ + 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..de435f96d 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); diff --git a/src/java/org/apache/fop/fo/flow/Block.java b/src/java/org/apache/fop/fo/flow/Block.java index daaebd6d0..e3176464d 100644 --- a/src/java/org/apache/fop/fo/flow/Block.java +++ b/src/java/org/apache/fop/fo/flow/Block.java @@ -21,6 +21,8 @@ package org.apache.fop.fo.flow; import java.awt.Color; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Length; import org.apache.fop.datatypes.Numeric; @@ -38,13 +40,13 @@ import org.apache.fop.fo.properties.CommonMarginBlock; import org.apache.fop.fo.properties.CommonRelativePosition; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.SpaceProperty; -import org.xml.sax.Locator; +import org.apache.fop.fo.properties.StructurePointerPropertySet; /** * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_block"> * <code>fo:block object</code></a>. */ -public class Block extends FObjMixed implements BreakPropertySet { +public class Block extends FObjMixed implements BreakPropertySet, StructurePointerPropertySet { // used for FO validation private boolean blockOrInlineItemFound = false; @@ -71,6 +73,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 +125,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 +175,11 @@ public class Block extends FObjMixed implements BreakPropertySet { return breakAfter; } + /** {@inheritDoc} */ + 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..7328b5644 100644 --- a/src/java/org/apache/fop/fo/flow/Character.java +++ b/src/java/org/apache/fop/fo/flow/Character.java @@ -38,12 +38,13 @@ import org.apache.fop.fo.properties.CommonTextDecoration; import org.apache.fop.fo.properties.KeepProperty; import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.SpaceProperty; +import org.apache.fop.fo.properties.StructurePointerPropertySet; /** * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_character"> * <code>fo:character</code></a> object. */ -public class Character extends FObj { +public class Character extends FObj implements StructurePointerPropertySet { // The value of properties relevant for fo:character. private CommonBorderPaddingBackground commonBorderPaddingBackground; private CommonFont commonFont; @@ -62,6 +63,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 +110,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} */ @@ -208,6 +211,11 @@ public class Character extends FObj { } /** {@inheritDoc} */ + 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..e6e8e9c01 100644 --- a/src/java/org/apache/fop/fo/flow/Inline.java +++ b/src/java/org/apache/fop/fo/flow/Inline.java @@ -26,17 +26,19 @@ import org.apache.fop.datatypes.Length; import org.apache.fop.fo.FONode; import org.apache.fop.fo.PropertyList; import org.apache.fop.fo.ValidationException; +import org.apache.fop.fo.properties.StructurePointerPropertySet; /** * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_inline"> * <code>fo:inline</code></a> formatting object. */ -public class Inline extends InlineLevel { +public class Inline extends InlineLevel implements StructurePointerPropertySet { // The value of properties relevant for fo:inline. // See also superclass 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 +68,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} */ @@ -145,6 +148,11 @@ public class Inline extends InlineLevel { } /** {@inheritDoc} */ + 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..34d267a64 100644 --- a/src/java/org/apache/fop/fo/flow/PageNumber.java +++ b/src/java/org/apache/fop/fo/flow/PageNumber.java @@ -34,12 +34,13 @@ import org.apache.fop.fo.properties.CommonBorderPaddingBackground; import org.apache.fop.fo.properties.CommonFont; import org.apache.fop.fo.properties.CommonTextDecoration; import org.apache.fop.fo.properties.SpaceProperty; +import org.apache.fop.fo.properties.StructurePointerPropertySet; /** * Class modelling the <a href="http://www.w3.org/TR/xsl/#fo_page-number"> * <code>fo:page-number</code></a> object. */ -public class PageNumber extends FObj { +public class PageNumber extends FObj implements StructurePointerPropertySet { // The value of properties relevant for fo:page-number. private CommonBorderPaddingBackground commonBorderPaddingBackground; private CommonFont commonFont; @@ -47,6 +48,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 +94,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 @@ -166,6 +169,11 @@ public class PageNumber extends FObj { } /** {@inheritDoc} */ + 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..ab8676cb3 100644 --- a/src/java/org/apache/fop/fo/flow/table/TableFObj.java +++ b/src/java/org/apache/fop/fo/flow/table/TableFObj.java @@ -19,6 +19,9 @@ package org.apache.fop.fo.flow.table; +import org.xml.sax.Attributes; +import org.xml.sax.Locator; + import org.apache.fop.apps.FOPException; import org.apache.fop.datatypes.Numeric; import org.apache.fop.datatypes.ValidationPercentBaseContext; @@ -33,19 +36,19 @@ import org.apache.fop.fo.properties.EnumProperty; import org.apache.fop.fo.properties.NumberProperty; import org.apache.fop.fo.properties.Property; import org.apache.fop.fo.properties.PropertyMaker; +import org.apache.fop.fo.properties.StructurePointerPropertySet; import org.apache.fop.layoutmgr.table.CollapsingBorderModel; -import org.xml.sax.Locator; -import org.xml.sax.Attributes; /** * Common base class for table-related FOs */ -public abstract class TableFObj extends FObj { +public abstract class TableFObj extends FObj implements StructurePointerPropertySet { private Numeric borderAfterPrecedence; private Numeric borderBeforePrecedence; private Numeric borderEndPrecedence; private Numeric borderStartPrecedence; + private String ptr; ConditionalBorder borderBefore; ConditionalBorder borderAfter; @@ -71,6 +74,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 +239,11 @@ public abstract class TableFObj extends FObj { } } + /** {@inheritDoc} */ + 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/fo/properties/StructurePointerPropertySet.java b/src/java/org/apache/fop/fo/properties/StructurePointerPropertySet.java new file mode 100644 index 000000000..5cce2822e --- /dev/null +++ b/src/java/org/apache/fop/fo/properties/StructurePointerPropertySet.java @@ -0,0 +1,34 @@ +/* + * 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.properties; + +/** + * Defines property access methods for internal structure pointer extension properties. + */ +public interface StructurePointerPropertySet { + + /** + * Returns the value of the "foi:ptr" property, the internal structure pointer used + * for tagged PDF and other formats that support a structure tree in addition to paged content. + * @return the "foi:ptr" property + */ + String getPtr(); + +} diff --git a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java index 126ab3796..53dc5b38c 100644 --- a/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java @@ -391,6 +391,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..eef649c97 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java @@ -19,13 +19,14 @@ package org.apache.fop.layoutmgr.inline; +import org.apache.fop.area.LinkResolver; +import org.apache.fop.area.Trait; +import org.apache.fop.area.inline.InlineArea; import org.apache.fop.datatypes.URISpecification; -import org.apache.fop.fo.flow.BasicLink; import org.apache.fop.fo.Constants; +import org.apache.fop.fo.flow.BasicLink; import org.apache.fop.layoutmgr.PageSequenceLayoutManager; -import org.apache.fop.area.inline.InlineArea; -import org.apache.fop.area.Trait; -import org.apache.fop.area.LinkResolver; +import org.apache.fop.layoutmgr.TraitSetter; /** * LayoutManager for the fo:basic-link formatting object @@ -56,6 +57,7 @@ public class BasicLinkLayoutManager extends InlineLayoutManager { private void setupBasicLinkArea(InlineArea area) { BasicLink fobj = (BasicLink) this.fobj; // internal destinations take precedence: + TraitSetter.addPtr(area, 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..b8a2b283f 100644 --- a/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java +++ b/src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java @@ -25,10 +25,13 @@ 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.fo.properties.StructurePointerPropertySet; import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontSelector; import org.apache.fop.layoutmgr.InlineKnuthSequence; @@ -504,12 +507,26 @@ public class TextLayoutManager extends LeafNodeLayoutManager { } TraitSetter.addFontTraits(textArea, font); textArea.addTrait(Trait.COLOR, this.foText.getColor()); - + TraitSetter.addPtr(textArea, 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 StructurePointerPropertySet) { + return (((StructurePointerPropertySet) fobj).getPtr()); + } else { + //No structure pointer applicable + return null; + } + } + 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/PDFAMode.java b/src/java/org/apache/fop/pdf/PDFAMode.java index 1b433e66d..18c4a2489 100644 --- a/src/java/org/apache/fop/pdf/PDFAMode.java +++ b/src/java/org/apache/fop/pdf/PDFAMode.java @@ -44,7 +44,18 @@ public final class PDFAMode { return this.name; } - /** @return true if this mode obey the restrictions established by PDF/A-1b. */ + /** + * Indicates whether this mode obeys the restrictions established by PDF/A-1a. + * @return true if this mode obeys the restrictions established by PDF/A-1a. + */ + public boolean isPDFA1LevelA() { + return (this != DISABLED); + } + + /** + * Indicates whether this mode obeys the restrictions established by PDF/A-1b. + * @return true if this mode obeys the restrictions established by PDF/A-1b. + */ public boolean isPDFA1LevelB() { return (this != DISABLED); //PDF/A-1a is a superset of PDF/A-1b! 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 a8d7a0702..db2e99875 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -179,13 +179,26 @@ public class PDFFactory { * @param cropBox the CropBox area * @param bleedBox the BleedBox area * @param trimBox the TrimBox area + * @param currentPageParentKey the integer key in the structural parent tree * * @return the created /Page object */ public PDFPage makePage(PDFResources resources, int pageIndex, Rectangle2D mediaBox, Rectangle2D cropBox, - Rectangle2D bleedBox, Rectangle2D trimBox) { + Rectangle2D bleedBox, Rectangle2D trimBox, + int currentPageParentKey) { + /* + * create a PDFPage with the next object number, the given + * resources, contents and dimensions + */ PDFPage page = new PDFPage(resources, pageIndex, mediaBox, cropBox, bleedBox, trimBox); + 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); @@ -207,7 +220,7 @@ public class PDFFactory { public PDFPage makePage(PDFResources resources, int pageWidth, int pageHeight, int pageIndex) { Rectangle2D mediaBox = new Rectangle2D.Double(0, 0, pageWidth, pageHeight); - return makePage(resources, pageIndex, mediaBox, mediaBox, mediaBox, mediaBox); + return makePage(resources, pageIndex, mediaBox, mediaBox, mediaBox, mediaBox, -1); } /** @@ -883,6 +896,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 6f5ffeb0d..66791e3ba 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 4fa3b0a09..1bcaa65c6 100644 --- a/src/java/org/apache/fop/pdf/PDFPage.java +++ b/src/java/org/apache/fop/pdf/PDFPage.java @@ -154,4 +154,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/PDFProfile.java b/src/java/org/apache/fop/pdf/PDFProfile.java index 20af4e212..b645cb825 100644 --- a/src/java/org/apache/fop/pdf/PDFProfile.java +++ b/src/java/org/apache/fop/pdf/PDFProfile.java @@ -58,9 +58,6 @@ public class PDFProfile { */ protected void validateProfileCombination() { if (pdfAMode != PDFAMode.DISABLED) { - if (pdfAMode == PDFAMode.PDFA_1A) { - throw new UnsupportedOperationException("PDF/A-1a is not implemented, yet"); - } if (pdfAMode == PDFAMode.PDFA_1B) { if (pdfXMode != PDFXMode.DISABLED && pdfXMode != PDFXMode.PDFX_3_2003) { throw new PDFConformanceException( @@ -192,6 +189,32 @@ public class PDFProfile { } } + /** + * Checks a few things required for tagged PDF. + */ + public void verifyTaggedPDF() { + if (getPDFAMode().isPDFA1LevelA()) { + final String err = "{0} requires the {1} dictionary entry to be set"; + PDFDictionary markInfo = getDocument().getRoot().getMarkInfo(); + if (markInfo == null) { + throw new PDFConformanceException(format( + "{0} requires the MarkInfo dictionary to be present", getPDFAMode())); + } + if (!Boolean.TRUE.equals(markInfo.get("Marked"))) { + throw new PDFConformanceException(format(err, + new Object[] {getPDFAMode(), "Marked"})); + } + if (getDocument().getRoot().getStructTreeRoot() == null) { + throw new PDFConformanceException(format(err, + new Object[] {getPDFAMode(), "StructTreeRoot"})); + } + if (getDocument().getRoot().getLanguage() == null) { + throw new PDFConformanceException(format(err, + new Object[] {getPDFAMode(), "Lang"})); + } + } + } + /** @return true if the ID entry must be present in the trailer. */ public boolean isIDEntryRequired() { return isPDFAActive() || isPDFXActive(); diff --git a/src/java/org/apache/fop/pdf/PDFRoot.java b/src/java/org/apache/fop/pdf/PDFRoot.java index 1ea316390..3057a9e4d 100644 --- a/src/java/org/apache/fop/pdf/PDFRoot.java +++ b/src/java/org/apache/fop/pdf/PDFRoot.java @@ -19,6 +19,9 @@ package org.apache.fop.pdf; +import java.io.IOException; +import java.io.OutputStream; + /** * Class representing a Root (/Catalog) object. */ @@ -56,18 +59,24 @@ public class PDFRoot extends PDFDictionary { * object must be created before the PDF document is * generated, but it is not assigned an object ID until * it is about to be written (immediately before the xref - * table as part of the trsailer). (mark-fop@inomial.com) + * table as part of the trailer). (mark-fop@inomial.com) * * @param objnum the object's number * @param pages the PDFPages object */ public PDFRoot(int objnum, PDFPages pages) { super(); - setObjectNumber(objnum); + setObjectNumber(objnum); put("Type", new PDFName("Catalog")); setRootPages(pages); } + /** {@inheritDoc} */ + protected int output(OutputStream stream) throws IOException { + getDocument().getProfile().verifyTaggedPDF(); + return super.output(stream); + } + /** * Set the page mode for the PDF document. * @@ -252,4 +261,39 @@ 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() + } + + /** + * Returns the MarkInfo dictionary. + * @return the MarkInfo dictionary (or null if it's not present) + */ + public PDFDictionary getMarkInfo() { + return (PDFDictionary)get("MarkInfo"); + } } 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..2f8898a19 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFStructElem.java @@ -0,0 +1,176 @@ +/* + * 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; + +import java.util.Locale; + +import org.apache.fop.util.XMLUtil; + +/** + * Class representing a PDF Structure Element. + */ +public class PDFStructElem extends PDFDictionary { + + private PDFObject parentObject = null; + private boolean level1 = false; + + /** + * Create the /StructTreeRoot dictionary + * @param parent Parent of this PDFStructElem + * @param structureType the structure type for the element + */ + public PDFStructElem(PDFObject parent, PDFName structureType) { + super(); + if (parent instanceof PDFStructElem) { + parentObject = (PDFStructElem) parent; + } + put("Type", new PDFName("StructElem")); + setStructureType(structureType); + setParent(parent); + } + + /** + * 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)); + } + } + + /** + * 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) { + PDFArray kids = getKids(); + if (kids == null) { + kids = new PDFArray(); + put("K", kids); + } + kids.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 == null || !mArray.contains(kid)) { + addKid(kid); + return true; + } else { + return false; + } + } + + /** + * Add kid referenced through mcid integer. Used for images. + * @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); + } + + /** + * Sets the structure type (the "S" entry). + * @param type the structure type + */ + public void setStructureType(PDFName type) { + put("S", type); + } + + /** + * Returns the structure type of this structure element. + * @return the structure type + */ + public PDFName getStructureType() { + return (PDFName)get("S"); + } + + /** + * Sets the language of this structure element. + * @param language the language (as defined in the section about + * "Natural Language Specification") + */ + public void setLanguage(String language) { + put("Lang", language); + } + + /** + * Sets the language of this structure element. + * @param language the language + */ + public void setLanguage(Locale language) { + setLanguage(XMLUtil.toRFC3066(language)); + } + + /** + * Returns the language of this structure element. + * @return the language (or null if no language was specified) + */ + public String getLanguage() { + return (String)get("Lang"); + } +} 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/render/AbstractRendererConfigurator.java b/src/java/org/apache/fop/render/AbstractRendererConfigurator.java index b31e5bfe6..c1a8bc182 100644 --- a/src/java/org/apache/fop/render/AbstractRendererConfigurator.java +++ b/src/java/org/apache/fop/render/AbstractRendererConfigurator.java @@ -20,6 +20,7 @@ package org.apache.fop.render; import org.apache.avalon.framework.configuration.Configuration; + import org.apache.fop.apps.FOUserAgent; /** @@ -45,7 +46,7 @@ public abstract class AbstractRendererConfigurator extends AbstractConfigurator */ protected Configuration getRendererConfig(Renderer renderer) { return super.getConfig(renderer.getMimeType()); - } + } /** * Returns the configuration subtree for a specific renderer. @@ -54,13 +55,13 @@ public abstract class AbstractRendererConfigurator extends AbstractConfigurator */ protected Configuration getRendererConfig(String mimeType) { return super.getConfig(mimeType); - } + } /** * {@inheritDoc} */ public String getType() { return TYPE; - } + } } diff --git a/src/java/org/apache/fop/render/afp/AFPEventProducer.xml b/src/java/org/apache/fop/render/afp/AFPEventProducer.xml new file mode 100644 index 000000000..23bd9a182 --- /dev/null +++ b/src/java/org/apache/fop/render/afp/AFPEventProducer.xml @@ -0,0 +1 @@ +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"/> diff --git a/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml index a05af3e21..47acd74ad 100644 --- a/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml +++ b/src/java/org/apache/fop/render/bitmap/BitmapRendererEventProducer.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.stoppingAfterFirstPageNoFilename">No filename information available. Stopping early after the first page.</message> <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.stoppingAfterFirstPageNoMultiWriter">Image writer does not support multiple images. Only the first page has been produced.</message> <message key="org.apache.fop.render.bitmap.BitmapRendererEventProducer.noImageWriterFound">Could not get an ImageWriter to produce "{mime}". The most likely explanation for this is a class loading problem.</message> 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/IFContext.java b/src/java/org/apache/fop/render/intermediate/IFContext.java index b05db1369..f052846d3 100644 --- a/src/java/org/apache/fop/render/intermediate/IFContext.java +++ b/src/java/org/apache/fop/render/intermediate/IFContext.java @@ -20,6 +20,7 @@ package org.apache.fop.render.intermediate; import java.util.Collections; +import java.util.Locale; import java.util.Map; import org.apache.xmlgraphics.util.QName; @@ -43,6 +44,10 @@ public class IFContext { /** foreign attributes: Map<QName, Object> */ private Map foreignAttributes = Collections.EMPTY_MAP; + private Locale language; + + private String structurePointer; + /** * Main constructor. * @param ua the user agent @@ -108,4 +113,55 @@ public class IFContext { setForeignAttributes(null); } + /** + * Sets the currently applicable language. + * @param lang the language + */ + public void setLanguage(Locale lang) { + this.language = lang; + } + + /** + * Returns the currently applicable language. + * @return the language (or null if the language is undefined) + */ + public Locale getLanguage() { + return this.language; + } + + /** + * Sets the structure pointer for the following painted marks. This method is used when + * accessibility features are enabled. + * @param ptr the structure pointer + */ + public void setStructurePointer(String ptr) { + this.structurePointer = ptr; + } + + /** + * Resets the current structure pointer. + * @see #setStructurePointer(String) + */ + public void resetStructurePointer() { + setStructurePointer(null); + } + + /** + * Returns the current structure pointer. + * @return the structure pointer (or null if no pointer is active) + * @see #setStructurePointer(String) + */ + public String getStructurePointer() { + return this.structurePointer; + } + + /** + * Indicates whether a structure pointer is available. + * @return true if there's a structure pointer + * @see #setStructurePointer(String) + */ + public boolean hasStructurePointer() { + return (this.structurePointer != null) && (structurePointer.length() > 0); + } + } diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java index fb9d54d88..e374f82fa 100644 --- a/src/java/org/apache/fop/render/intermediate/IFParser.java +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -25,6 +25,7 @@ import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.util.Map; +import java.util.Set; import javax.xml.transform.Source; import javax.xml.transform.Transformer; @@ -73,6 +74,15 @@ public class IFParser implements IFConstants { private static SAXTransformerFactory tFactory = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + private static Set handledNamespaces = new java.util.HashSet(); + + static { + handledNamespaces.add(XMLNS_NAMESPACE_URI); + handledNamespaces.add(XML_NAMESPACE); + handledNamespaces.add(NAMESPACE); + handledNamespaces.add(XLINK_NAMESPACE); + } + /** * Parses an intermediate file and paints it. * @param src the Source instance pointing to the intermediate file @@ -173,11 +183,18 @@ public class IFParser implements IFConstants { documentHandler.getContext().resetForeignAttributes(); } + private void establishStructurePointer(String ptr) { + documentHandler.getContext().setStructurePointer(ptr); + } + + private void resetStructurePointer() { + documentHandler.getContext().resetStructurePointer(); + } + /** {@inheritDoc} */ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if (delegate != null) { - //delegateStack.push(qName); delegateDepth++; delegate.startElement(uri, localName, qName, attributes); } else { @@ -194,8 +211,6 @@ public class IFParser implements IFConstants { } catch (IFException ife) { handleIFException(ife); } - } else if ("extension-attachments".equals(localName)) { - //TODO implement me } else { handled = false; } @@ -352,6 +367,11 @@ public class IFParser implements IFConstants { public void startElement(Attributes attributes) throws IFException { String id = attributes.getValue("id"); + String xmllang = attributes.getValue(XML_NAMESPACE, "lang"); + if (xmllang != null) { + documentHandler.getContext().setLanguage( + XMLUtil.convertRFC3066ToLocale(xmllang)); + } Map foreignAttributes = getForeignAttributes(lastAttributes); establishForeignAttributes(foreignAttributes); documentHandler.startPageSequence(id); @@ -360,6 +380,7 @@ public class IFParser implements IFConstants { public void endElement() throws IFException { documentHandler.endPageSequence(); + documentHandler.getContext().setLanguage(null); } } @@ -484,7 +505,10 @@ public class IFParser implements IFConstants { s = lastAttributes.getValue("word-spacing"); int wordSpacing = (s != null ? Integer.parseInt(s) : 0); int[] dx = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx"); + String ptr = lastAttributes.getValue("ptr"); // used for accessibility + establishStructurePointer(ptr); painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString()); + resetStructurePointer(); } public boolean ignoreCharacters() { @@ -579,6 +603,8 @@ 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 + establishStructurePointer(ptr); if (foreignObject != null) { painter.drawImage(foreignObject, new Rectangle(x, y, width, height)); @@ -592,6 +618,7 @@ public class IFParser implements IFConstants { painter.drawImage(uri, new Rectangle(x, y, width, height)); } resetForeignAttributes(); + resetStructurePointer(); inForeignObject = false; } @@ -632,11 +659,7 @@ public class IFParser implements IFConstants { for (int i = 0, c = atts.getLength(); i < c; i++) { String ns = atts.getURI(i); if (ns.length() > 0) { - if ("http://www.w3.org/2000/xmlns/".equals(ns)) { - continue; - } else if (NAMESPACE.equals(ns)) { - continue; - } else if (XLINK_NAMESPACE.equals(ns)) { + if (handledNamespaces.contains(ns)) { continue; } if (foreignAttributes == null) { diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index e22088824..2a23d06f8 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -30,6 +30,7 @@ import java.io.OutputStream; import java.util.Arrays; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Stack; @@ -493,6 +494,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { try { if (this.inPageSequence) { documentHandler.endPageSequence(); + documentHandler.getContext().setLanguage(null); } else { if (this.documentMetadata == null) { this.documentMetadata = createDefaultDocumentMetadata(); @@ -502,6 +504,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { this.inPageSequence = true; } establishForeignAttributes(pageSequence.getForeignAttributes()); + documentHandler.getContext().setLanguage(toLocale(pageSequence)); documentHandler.startPageSequence(null); resetForeignAttributes(); processExtensionAttachments(pageSequence); @@ -510,6 +513,17 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } } + private Locale toLocale(PageSequence pageSequence) { + if (pageSequence.getLanguage() != null) { + if (pageSequence.getCountry() != null) { + return new Locale(pageSequence.getLanguage(), pageSequence.getCountry()); + } else { + return new Locale(pageSequence.getLanguage()); + } + } + return null; + } + private Metadata createDefaultDocumentMetadata() { Metadata xmp = new Metadata(); DublinCoreAdapter dc = DublinCoreSchema.getAdapter(xmp); @@ -604,6 +618,14 @@ public class IFRenderer extends AbstractPathOrientedRenderer { documentHandler.getContext().resetForeignAttributes(); } + private void establishStructurePointer(String ptr) { + documentHandler.getContext().setStructurePointer(ptr); + } + + private void resetStructurePointer() { + documentHandler.getContext().resetStructurePointer(); + } + /** {@inheritDoc} */ protected void saveGraphicsState() { graphicContextStack.push(graphicContext); @@ -824,17 +846,20 @@ public class IFRenderer extends AbstractPathOrientedRenderer { currentIPPosition = saveIP; currentBPPosition = saveBP; - currentBPPosition += (int)(bv.getAllocBPD()); + currentBPPosition += bv.getAllocBPD(); } viewportDimensionStack.pop(); } /** {@inheritDoc} */ public void renderViewport(Viewport viewport) { + String ptr = (String) viewport.getTrait(Trait.PTR); + establishStructurePointer(ptr); Dimension dim = new Dimension(viewport.getIPD(), viewport.getBPD()); viewportDimensionStack.push(dim); super.renderViewport(viewport); viewportDimensionStack.pop(); + resetStructurePointer(); } /** {@inheritDoc} */ @@ -892,6 +917,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(); @@ -935,6 +961,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); } @@ -969,6 +996,8 @@ 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 + establishStructurePointer(ptr); // This assumes that *all* CIDFonts use a /ToUnicode mapping Typeface tf = getTypeface(fontName); @@ -990,6 +1019,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { textUtil.flush(); renderTextDecoration(tf, size, text, bl, rx); + resetStructurePointer(); } /** {@inheritDoc} */ @@ -1060,10 +1090,10 @@ 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; void addChar(char ch) { text.append(ch); diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 695514776..2d009d58d 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -25,11 +25,26 @@ 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.Locale; import java.util.Map; +import java.util.Stack; + +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 +75,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 +202,16 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler handler.startPrefixMapping(DocumentNavigationExtensionConstants.PREFIX, DocumentNavigationExtensionConstants.NAMESPACE); handler.startElement(EL_DOCUMENT); + if (this.getUserAgent().isAccessibilityEnabled()) { + 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,10 +269,34 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (id != null) { atts.addAttribute(XML_NAMESPACE, "id", "xml:id", XMLUtil.CDATA, id); } + applyLanguage(atts); addForeignAttributes(atts); handler.startElement(EL_PAGE_SEQUENCE, atts); + if (this.getUserAgent().isAccessibilityEnabled()) { + 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); } } @@ -221,6 +304,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler public void endPageSequence() throws IFException { try { handler.endElement(EL_PAGE_SEQUENCE); + popLanguage(); } catch (SAXException e) { throw new IFException("SAX error in endPageSequence()", e); } @@ -392,6 +476,10 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); + String ptr = getContext().getStructurePointer(); + 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); @@ -418,6 +506,10 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); + String ptr = getContext().getStructurePointer(); + 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); @@ -531,6 +623,10 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (dx != null) { addAttribute(atts, "dx", IFUtil.toString(dx)); } + String ptr = getContext().getStructurePointer(); + 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); @@ -739,4 +835,25 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } } + private Stack languageStack = new Stack(); + + private void applyLanguage(AttributesImpl atts) { + Locale lang = getContext().getLanguage(); + if (lang != null) { + if (languageStack.isEmpty() || !languageStack.peek().equals(lang)) { + atts.addAttribute(XML_NAMESPACE, "lang", "xml:lang", XMLUtil.CDATA, + XMLUtil.toRFC3066(lang)); + } + languageStack.push(lang); + } else { + assert languageStack.isEmpty(); + } + } + + private void popLanguage() { + if (!languageStack.isEmpty()) { + languageStack.pop(); + } + } + } 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/pcl/PCLEventProducer.xml b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml index a3b36fd60..d4fe60b2f 100644 --- a/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml +++ b/src/java/org/apache/fop/render/pcl/PCLEventProducer.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.pcl.PCLEventProducer.paperTypeUnavailable">Paper type ({pageWidth} x {pageHeight} mpt) could not be determined. Falling back to: {fallbackPaper}</message> </catalogue> diff --git a/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java b/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java new file mode 100644 index 000000000..f0cc39698 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java @@ -0,0 +1,141 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* $Id$ */ + +package org.apache.fop.render.pdf; + +import java.util.Map; + +import org.apache.fop.pdf.PDFName; +import org.apache.fop.pdf.PDFObject; +import org.apache.fop.pdf.PDFStructElem; + +/** + * This class provides the standard mappings from Formatting Objects to PDF structure types. + */ +public class FOToPDFRoleMap { + + private static final Map STANDARD_MAPPINGS = new java.util.HashMap(); + + private static final PDFName TFOOT = new PDFName("TFoot"); + private static final PDFName THEAD = new PDFName("THead"); + + static { + addMapping("block", "P"); + addMapping("block-container", "Div"); + + PDFName st = new PDFName("Span"); + addMapping("inline", st); + addMapping("wrapper", st); + addMapping("character", st); + + addMapping("root", "Document"); + addMapping("page-sequence", "Part"); + addMapping("flow", "Sect"); + addMapping("static-content", "Sect"); + + st = new PDFName("Quote"); + addMapping("page-number", st); + addMapping("page-number-citation", st); + addMapping("page-number-citation-last", st); + + st = new PDFName("Figure"); + addMapping("external-graphic", st); + addMapping("instream-foreign-object", st); + + addMapping("table", "Table"); + addMapping("table-body", "TBody"); + addMapping("table-header", THEAD); + addMapping("table-footer", TFOOT); + addMapping("table-row", "TR"); + addMapping("table-cell", new TableCellMapper()); + + addMapping("list-block", "L"); + addMapping("list-item", "LI"); + addMapping("list-item-label", "Lbl"); + addMapping("list-item-body", "LBody"); + + addMapping("basic-link", "Link"); + addMapping("footnote", "Note"); + addMapping("footnote-body", "Sect"); + addMapping("marker", "Private"); + } + + private static void addMapping(String fo, String pdfName) { + addMapping(fo, new PDFName(pdfName)); + } + + private static void addMapping(String fo, PDFName pdfName) { + addMapping(fo, new SimpleMapper(pdfName)); + } + + private static void addMapping(String fo, Mapper mapper) { + STANDARD_MAPPINGS.put(fo, mapper); + } + + /** + * Maps a Formatting Object to a PDFName representing the associated structure type. + * @param fo the formatting object's local name + * @param parent the parent of the structure element to be mapped + * @return the structure type or null if no match could be found + */ + public static PDFName mapFormattingObject(String fo, PDFObject parent) { + Mapper mapper = (Mapper)STANDARD_MAPPINGS.get(fo); + if (mapper != null) { + return mapper.getStructureType(parent); + } + return null; + } + + private interface Mapper { + PDFName getStructureType(PDFObject parent); + } + + private static class SimpleMapper implements Mapper { + + private PDFName structureType; + + public SimpleMapper(PDFName structureType) { + this.structureType = structureType; + } + + public PDFName getStructureType(PDFObject parent) { + return structureType; + } + + } + + private static class TableCellMapper implements Mapper { + + private static final PDFName TD = new PDFName("TD"); + private static final PDFName TH = new PDFName("TH"); + + public PDFName getStructureType(PDFObject parent) { + PDFStructElem grandParent = (PDFStructElem) + ((PDFStructElem)parent).getParentStructElem(); + //TODO What to do with cells from table-footer? Currently they are mapped on TD. + if (THEAD.equals(grandParent.getStructureType())) { + return TH; + } else { + return TD; + } + } + + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java index 841dd7e01..d5e6b0b63 100644 --- a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java +++ b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java @@ -19,6 +19,7 @@ package org.apache.fop.render.pdf; + /** * Constants used for configuring PDF output. */ diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java index 45fb7ec51..73e1945f2 100644 --- a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -56,6 +56,8 @@ public class PDFContentGenerator { /** Text generation utility holding the current font status */ protected PDFTextUtil textutil; + private boolean inMarkedContentSequence; + private boolean inArtifactMode; /** * Main constructor. Creates a new PDF stream and additional helper classes for text painting @@ -153,6 +155,40 @@ public class PDFContentGenerator { currentStream.add("q\n"); } + /** {@inheritDoc} */ + protected void saveGraphicsState(String structElemType, int sequenceNum) { + endTextObject(); + currentState.save(); + beginMarkedContentSequence(structElemType, sequenceNum); + currentStream.add("q\n"); + } + + /** + * Begins a new marked content sequence (BDC or BMC). If the parameter structElemType is null, + * the sequenceNum is ignored and instead of a BDC with the MCID as parameter, an "Artifact" + * and a BMC command is generated. + * @param structElemType Structure Element Type + * @param sequenceNum Sequence number + */ + protected void beginMarkedContentSequence(String structElemType, int sequenceNum) { + assert !this.inMarkedContentSequence; + assert !this.inArtifactMode; + if (structElemType != null) { + currentStream.add(structElemType + " <</MCID " + String.valueOf(sequenceNum) + ">>\n" + + "BDC\n"); + } else { + currentStream.add("/Artifact\nBMC\n"); + this.inArtifactMode = true; + } + this.inMarkedContentSequence = true; + } + + private void endMarkedContentSequence() { + currentStream.add("EMC\n"); + this.inMarkedContentSequence = false; + this.inArtifactMode = false; + } + /** * 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 +207,38 @@ public class PDFContentGenerator { restoreGraphicsState(true); } + /** used for accessibility */ + protected void restoreGraphicsStateAccess() { + endTextObject(); + currentStream.add("Q\n"); + if (this.inMarkedContentSequence) { + endMarkedContentSequence(); + } + 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(); + endMarkedContentSequence(); + beginMarkedContentSequence(structElemType, mcid); + textutil.beginTextObject(); + } + + /** + * used for accessibility + * separates a text element from fo:leader text element + */ + public void separateTextElementFromLeader() { + if (!inArtifactMode) { + separateTextElements(0, null); + } + } + /** Indicates the beginning of a text object. */ protected void beginTextObject() { if (!textutil.isInTextObject()) { @@ -178,9 +246,31 @@ public class PDFContentGenerator { } } + /** + * Accessibility beginTextObject + * @param mcid of text element + * @param structElemType of parent + */ + protected void beginTextObjectAccess(int mcid, String structElemType) { + if (!textutil.isInTextObject()) { + beginMarkedContentSequence(structElemType, mcid); + textutil.beginTextObject(); + } + } + + /** + * Accessibility begin of LeaderTextObject + */ + public void beginLeaderTextObject() { + beginTextObjectAccess(0, null); + } + /** Indicates the end of a text object. */ protected void endTextObject() { if (textutil.isInTextObject()) { + if (this.inMarkedContentSequence) { + endMarkedContentSequence(); + } textutil.endTextObject(); } } @@ -326,5 +416,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 12f99780b..9e2b2cdb3 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -25,22 +25,47 @@ import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.geom.Rectangle2D.Double; +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.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; 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.extensions.prepress.PageBoundaries; import org.apache.fop.render.extensions.prepress.PageScale; import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; @@ -49,6 +74,7 @@ import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; import org.apache.fop.render.intermediate.IFDocumentNavigationHandler; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFPainter; +import org.apache.fop.util.XMLUtil; /** * {@code IFDocumentHandler} implementation that produces PDF. @@ -58,6 +84,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 reducedFOTree = 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; @@ -85,7 +175,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); /** @@ -96,7 +186,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public boolean supportsPagesOutOfOrder() { - return true; + return !accessEnabled; } /** {@inheritDoc} */ @@ -129,8 +219,28 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { super.startDocument(); try { this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream); + this.accessEnabled = getUserAgent().isAccessibilityEnabled(); + if (accessEnabled) { + this.pdfDoc.getRoot().makeTagged(); + log.info("Accessibility is enabled"); + PDFStructTreeRoot structTreeRoot = this.pdfDoc.getFactory().makeStructTreeRoot(); + this.pdfDoc.getRoot().setStructTreeRoot(structTreeRoot); + PDFStructElem structElemDocument = new PDFStructElem(structTreeRoot, + FOToPDFRoleMap.mapFormattingObject("root", structTreeRoot)); + this.pdfDoc.assignObjectNumber(structElemDocument); + this.pdfDoc.addTrailerObject(structElemDocument); + structTreeRoot.addKid(structElemDocument); + + 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); } } @@ -143,8 +253,33 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { public void endDocument() throws IFException { try { pdfDoc.getResources().addFonts(pdfDoc, fontInfo); - pdfDoc.outputTrailer(this.outputStream); - + if (getUserAgent().isAccessibilityEnabled()) { + 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; + reducedFOTree = null; + structElemType = null; + parentTree = null; + structTreeMap = null; + parentTreeList = null; + } else { + pdfDoc.outputTrailer(this.outputStream); + } this.pdfDoc = null; pdfResources = null; @@ -157,9 +292,83 @@ 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 + //TODO page sequence title + + if (this.pdfDoc.getRoot().getLanguage() == null + && getContext().getLanguage() != null) { + //No document-level language set, so we use the first page-sequence's language + this.pdfDoc.getRoot().setLanguage(XMLUtil.toRFC3066(getContext().getLanguage())); + } + + if (getUserAgent().isAccessibilityEnabled()) { + try { + if (this.pdfDoc.getRoot().getLanguage() == null) { + String fallbackLanguage; + if (this.pdfDoc.getProfile().getPDFAMode().isPDFA1LevelA()) { + //According to Annex B of ISO-19005-1:2005(E), section B.2 + fallbackLanguage = "x-unknown"; + } else { + //No language has been set on the first page-sequence, so fall back to "en". + fallbackLanguage = "en"; + } + this.pdfDoc.getRoot().setLanguage(fallbackLanguage); + } + + if (reducedFOTree == null) { + reducedFOTree = parser.parse( + new ByteArrayInputStream(this.getUserAgent().getReducedFOTree())); + } + PDFStructElem parent = (PDFStructElem)getStructTreeRoot().getFirstChild(); + PDFStructElem structElemPart = new PDFStructElem(parent, + FOToPDFRoleMap.mapFormattingObject("page-sequence", parent)); + if (getContext().getLanguage() != null) { + structElemPart.setLanguage(getContext().getLanguage()); + } + 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, reducedFOTree, + 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(structElemPart, + FOToPDFRoleMap.mapFormattingObject(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} */ @@ -170,6 +379,11 @@ 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(); PageBoundaries boundaries = new PageBoundaries(size, getContext().getForeignAttributes()); @@ -196,14 +410,16 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { toPointAndScale(mediaBox, scaleX, scaleY), toPointAndScale(cropBox, scaleX, scaleY), toPointAndScale(bleedBox, scaleX, scaleY), - toPointAndScale(trimBox, scaleX, scaleY)); + toPointAndScale(trimBox, scaleX, scaleY), + parentTreeKey); 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); // 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, (scaleY * size.height) / 1000f); @@ -266,8 +482,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(); @@ -283,4 +499,196 @@ 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) { + PDFStructElem tempStructElem = (PDFStructElem) structTreeMap.get(ptr); + if (tempStructElem != null) { + PDFDictionary dict = new PDFDictionary(); + dict.put("Type", new PDFName("MCR")); + dict.put("Pg", this.currentPage); + dict.put("MCID", mcid); + tempStructElem.addKid(dict); + if (!tempStructElem.getLevel1()) { + addMeToParent(tempStructElem); + } + } + //tempStructElem is null, for example inside fo:leaders in which case + //the text shall be marked as artifact + } + + /** + * 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(parent, + FOToPDFRoleMap.mapFormattingObject(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 3e1024d98..138129334 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java @@ -25,6 +25,9 @@ import java.awt.geom.Rectangle2D; import java.util.Iterator; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.apache.fop.pdf.PDFAction; import org.apache.fop.pdf.PDFDocument; import org.apache.fop.pdf.PDFFactory; @@ -46,11 +49,11 @@ import org.apache.fop.render.pdf.PDFDocumentHandler.PageReference; * 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 != null && 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..fe3a00aa8 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().isAccessibilityEnabled()) { + String structElemType = pdfContext.getStructElemType(); + if (structElemType != null && 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..24d17a2b1 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().isAccessibilityEnabled()) { + 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..fde9e0696 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().isAccessibilityEnabled()) { + String structElemType = pdfContext.getStructElemType(); + int sequenceNum = pdfContext.getSequenceNum(); + generator.beginMarkedContentSequence(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().isAccessibilityEnabled()) { + 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..4c9baf0f6 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().isAccessibilityEnabled(); } /** {@inheritDoc} */ @@ -122,22 +129,60 @@ public class PDFPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect) + throws IFException { PDFXObject xobject = getPDFDoc().getXObject(uri); if (xobject != null) { - placeImage(rect, xobject); + if (accessEnabled && getContext().hasStructurePointer()) { + String ptr = getContext().getStructurePointer(); + prepareImageMCID(ptr); + placeImageAccess(rect, xobject); + addImageMCID(ptr); + } else { + placeImage(rect, xobject); + } return; } + if (accessEnabled && getContext().hasStructurePointer()) { + String ptr = getContext().getStructurePointer(); + prepareImageMCID(ptr); + drawImageUsingURI(uri, rect); + addImageMCID(ptr); + } else { + drawImageUsingURI(uri, rect); + } + flushPDFDoc(); + } - drawImageUsingURI(uri, rect); + private void prepareImageMCID(String ptr) { + mcid = this.documentHandler.getMCID(); + mcid++; // fix for Acro Checker + this.documentHandler.incMCID(); // simulating a parent text element + structElemType = this.documentHandler.getStructElemType(ptr); + if (structElemType != null) { + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getParentTrailerObject(ptr)); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + } + } - flushPDFDoc(); + private void addImageMCID(String ptr) { + if (this.structElemType != null) { + this.documentHandler.addChildToStructElemImage(ptr, mcid); + this.documentHandler.incMCID(); + } + //If structElemType is null, it means "Artifact" mode (ex. leader with use-content). } /** {@inheritDoc} */ protected RenderingContext createRenderingContext() { PDFRenderingContext pdfContext = new PDFRenderingContext( getUserAgent(), generator, this.documentHandler.currentPage, getFontInfo()); + pdfContext.setMCID(mcid); + pdfContext.setStructElemType(structElemType); return pdfContext; } @@ -158,11 +203,34 @@ 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); - + if (accessEnabled && getContext().hasStructurePointer()) { + String ptr = getContext().getStructurePointer(); + prepareImageMCID(ptr); + drawImageUsingDocument(doc, rect); + addImageMCID(ptr); + } else { + drawImageUsingDocument(doc, rect); + } flushPDFDoc(); } @@ -253,10 +321,40 @@ 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) throws IFException { - generator.updateColor(state.getTextColor(), true, null); - generator.beginTextObject(); + if (accessEnabled) { + String ptr = getContext().getStructurePointer(); + int mcId; + String structElType = null; + if (ptr != null && ptr.length() > 0) { + mcId = this.documentHandler.getMCID(); + structElType = this.documentHandler.getStructElemType(ptr); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(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"> + // Leader content is marked as "/Artifact" + 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 +375,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/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..022b7111e 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -37,6 +37,7 @@ import org.apache.xmlgraphics.xmp.Metadata; import org.apache.xmlgraphics.xmp.schemas.XMPBasicAdapter; import org.apache.xmlgraphics.xmp.schemas.XMPBasicSchema; +import org.apache.fop.accessibility.AccessibilityUtil; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.extensions.xmp.XMPMetadata; import org.apache.fop.pdf.PDFAMode; @@ -109,7 +110,7 @@ class PDFRenderingUtil implements PDFConfigurationConstants { private void initialize() { PDFEncryptionParams params - = (PDFEncryptionParams)userAgent.getRendererOptions().get(ENCRYPTION_PARAMS); + = (PDFEncryptionParams)userAgent.getRendererOptions().get(ENCRYPTION_PARAMS); if (params != null) { this.encryptionParams = params; //overwrite if available } @@ -161,6 +162,10 @@ class PDFRenderingUtil implements PDFConfigurationConstants { if (s != null) { this.pdfAMode = PDFAMode.valueOf(s); } + if (this.pdfAMode.isPDFA1LevelA()) { + //Enable accessibility if PDF/A-1a is enabled because it requires tagged PDF. + userAgent.getRendererOptions().put(AccessibilityUtil.ACCESSIBILITY, Boolean.TRUE); + } s = (String)userAgent.getRendererOptions().get(PDF_X_MODE); if (s != null) { this.pdfXMode = PDFXMode.valueOf(s); diff --git a/src/java/org/apache/fop/render/ps/PSEventProducer.xml b/src/java/org/apache/fop/render/ps/PSEventProducer.xml index a0078223a..f2fe60497 100644 --- a/src/java/org/apache/fop/render/ps/PSEventProducer.xml +++ b/src/java/org/apache/fop/render/ps/PSEventProducer.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="org.apache.fop.render.ps.PSEventProducer.postscriptDictionaryParseError">Failed to parse dictionary string. Reason: {e}, content = "{content}"</message> </catalogue> diff --git a/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml b/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml index 8f1f21a81..e81a7515f 100644 --- a/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml +++ b/src/java/org/apache/fop/render/rtf/RTFEventProducer.xml @@ -1,5 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> -<catalogue xml:lang="en"> +<?xml version="1.0" encoding="UTF-8"?><catalogue xml:lang="en"> <message key="locator">[ (See position {loc})| (See {#gatherContextInfo})| (No context info available)]</message> <message key="org.apache.fop.render.rtf.RTFEventProducer.onlySPMSupported">Only simple-page-masters are supported on page-sequences. Using default simple-page-master from page-sequence-master "{masterReference}".{{locator}}</message> <message key="org.apache.fop.render.rtf.RTFEventProducer.noSPMFound">No simple-page-master could be determined.</message> diff --git a/src/java/org/apache/fop/render/txt/TXTRenderer.java b/src/java/org/apache/fop/render/txt/TXTRenderer.java index 575f1232f..3e2fab230 100644 --- a/src/java/org/apache/fop/render/txt/TXTRenderer.java +++ b/src/java/org/apache/fop/render/txt/TXTRenderer.java @@ -28,6 +28,8 @@ import java.io.OutputStream; import java.util.List; import java.util.Map; +import org.apache.xmlgraphics.util.UnitConv; + import org.apache.fop.apps.FOPException; import org.apache.fop.area.Area; import org.apache.fop.area.CTM; @@ -37,7 +39,6 @@ import org.apache.fop.area.inline.TextArea; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.txt.border.AbstractBorderElement; import org.apache.fop.render.txt.border.BorderManager; -import org.apache.xmlgraphics.util.UnitConv; /** * Renderer that renders areas to plain text. diff --git a/src/java/org/apache/fop/util/DOM2SAX.java b/src/java/org/apache/fop/util/DOM2SAX.java index 839cf107f..39d2af4a1 100644 --- a/src/java/org/apache/fop/util/DOM2SAX.java +++ b/src/java/org/apache/fop/util/DOM2SAX.java @@ -26,7 +26,6 @@ import java.util.Stack; import org.w3c.dom.Document; import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; - import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; import org.xml.sax.ext.LexicalHandler; @@ -79,6 +78,15 @@ public class DOM2SAX { } /** + * 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 * only if the prefix is unknown or it is mapped to a different URI. */ diff --git a/src/java/org/apache/fop/util/XMLUtil.java b/src/java/org/apache/fop/util/XMLUtil.java index e42bef90e..644b7c22d 100644 --- a/src/java/org/apache/fop/util/XMLUtil.java +++ b/src/java/org/apache/fop/util/XMLUtil.java @@ -21,6 +21,7 @@ package org.apache.fop.util; import java.awt.Rectangle; import java.awt.geom.Rectangle2D; +import java.util.Locale; import org.xml.sax.Attributes; import org.xml.sax.SAXException; @@ -170,4 +171,39 @@ public class XMLUtil implements XMLConstants { atts.addAttribute("", localName, localName, XMLUtil.CDATA, value); } + /** + * Converts a {@link Locale} instance to an RFC 3066 compliant language identifier. + * @param language the language + * @return the formatted language identifier + */ + public static String toRFC3066(Locale language) { + if (language == null || language.getLanguage().length() == 0) { + return null; + } + StringBuffer sb = new StringBuffer(); + sb.append(language.getLanguage()); + if (language.getCountry().length() > 0) { + sb.append('-'); + sb.append(language.getCountry()); + } + return sb.toString(); + } + + /** + * Converts an RFC 3066 compliant language identifier to a {@link Locale} instance. + * @param lang the language string + * @return the converted locale instance + */ + public static Locale convertRFC3066ToLocale(String lang) { + if (lang == null || lang.length() == 0) { + return null; + } + String[] parts = lang.split("-"); + if (parts.length == 1) { + return new Locale(parts[0]); + } else { + return new Locale(parts[0], parts[1]); + } + } + } |