diff options
author | Vincent Hennebert <vhennebert@apache.org> | 2009-10-27 19:07:52 +0000 |
---|---|---|
committer | Vincent Hennebert <vhennebert@apache.org> | 2009-10-27 19:07:52 +0000 |
commit | be1d3250bd8dd170d4fe4fe8a4850ca7759cd079 (patch) | |
tree | 3ce075e8b992e368e4d06935d3b317faaa622579 /src/java | |
parent | d7b69927f611f4da9cc9b26396e1eb0a1bdae39e (diff) | |
parent | cea774bc9129a1e2b6f47cff523cf0e1c13fa832 (diff) | |
download | xmlgraphics-fop-be1d3250bd8dd170d4fe4fe8a4850ca7759cd079.tar.gz xmlgraphics-fop-be1d3250bd8dd170d4fe4fe8a4850ca7759cd079.zip |
Merged back Temp_Accessibility branch into Trunk
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@830293 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java')
79 files changed, 2842 insertions, 124 deletions
diff --git a/src/java/META-INF/services/org.apache.fop.fo.ElementMapping b/src/java/META-INF/services/org.apache.fop.fo.ElementMapping index 0194b19db..b62f36196 100644 --- a/src/java/META-INF/services/org.apache.fop.fo.ElementMapping +++ b/src/java/META-INF/services/org.apache.fop.fo.ElementMapping @@ -2,9 +2,10 @@ org.apache.fop.fo.FOElementMapping org.apache.fop.fo.extensions.svg.SVGElementMapping org.apache.fop.fo.extensions.svg.BatikExtensionElementMapping org.apache.fop.fo.extensions.ExtensionElementMapping +org.apache.fop.fo.extensions.InternalElementMapping org.apache.fop.fo.extensions.OldExtensionElementMapping org.apache.fop.fo.extensions.xmp.XMPElementMapping org.apache.fop.fo.extensions.xmp.RDFElementMapping org.apache.fop.render.ps.extensions.PSExtensionElementMapping org.apache.fop.render.afp.extensions.AFPElementMapping -org.apache.fop.render.pcl.extensions.PCLElementMapping
\ No newline at end of file +org.apache.fop.render.pcl.extensions.PCLElementMapping diff --git a/src/java/org/apache/fop/accessibility/Accessibility.java b/src/java/org/apache/fop/accessibility/Accessibility.java new file mode 100644 index 000000000..d550b433a --- /dev/null +++ b/src/java/org/apache/fop/accessibility/Accessibility.java @@ -0,0 +1,87 @@ +/* + * 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.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamSource; + +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.fop.apps.FOPException; +import org.apache.fop.apps.FOUserAgent; + +/** + * Helper class for FOP's accessibility features. + */ +public final class Accessibility { + + /** Constant string for the rendering options key to enable accessibility features. */ + public static final String ACCESSIBILITY = "accessibility"; + + // TODO what if the default factory is not a SAXTransformerFactory? + private static SAXTransformerFactory tfactory + = (SAXTransformerFactory)SAXTransformerFactory.newInstance(); + + private static Templates addPtrTemplates; + + private static Templates reduceFOTreeTemplates; + + private Accessibility() { } + + /** + * Decorates the given handler 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 { + try { + setupTemplates(); + TransformerHandler addPtr = tfactory.newTransformerHandler(addPtrTemplates); + Transformer reduceFOTree = reduceFOTreeTemplates.newTransformer(); + return new AccessibilityPreprocessor(addPtr, reduceFOTree, userAgent, handler); + } catch (TransformerConfigurationException e) { + throw new FOPException(e); + } + } + + private static synchronized void setupTemplates() throws TransformerConfigurationException { + if (addPtrTemplates == null) { + addPtrTemplates = loadTemplates("addPtr.xsl"); + } + if (reduceFOTreeTemplates == null) { + reduceFOTreeTemplates = loadTemplates("reduceFOTree.xsl"); + } + } + + private static Templates loadTemplates(String source) throws TransformerConfigurationException { + Source src = new StreamSource(Accessibility.class.getResource(source).toExternalForm()); + return tfactory.newTemplates(src); + } + +} diff --git a/src/java/org/apache/fop/accessibility/AccessibilityEventProducer.java b/src/java/org/apache/fop/accessibility/AccessibilityEventProducer.java new file mode 100644 index 000000000..9a74b7ca8 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/AccessibilityEventProducer.java @@ -0,0 +1,54 @@ +/* + * 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 org.apache.fop.events.EventBroadcaster; +import org.apache.fop.events.EventProducer; + +/** + * Event producer for accessibility-related events. + */ +public interface AccessibilityEventProducer extends EventProducer { + + /** Provider class for the event producer. */ + public final class Provider { + + private Provider() { } + + /** + * Returns an event producer. + * + * @param broadcaster the event broadcaster to use + * @return the event producer + */ + public static AccessibilityEventProducer get(EventBroadcaster broadcaster) { + return (AccessibilityEventProducer) broadcaster.getEventProducerFor( + AccessibilityEventProducer.class); + } + } + + /** + * The structure tree is missing in the XML file. + * + * @param source the event source + * @event.severity FATAL + */ + void noStructureTreeInXML(Object source); +} diff --git a/src/java/org/apache/fop/accessibility/AccessibilityEventProducer.xml b/src/java/org/apache/fop/accessibility/AccessibilityEventProducer.xml new file mode 100644 index 000000000..70466c2e9 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/AccessibilityEventProducer.xml @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="UTF-8"?> +<catalogue xml:lang="en"> + <message key="org.apache.fop.accessibility.AccessibilityEventProducer.noStructureTreeInXML">Accessibility is enabled but structure tree is missing in XML file. Please disable accessibility, or re-generate XML file in accessibility mode.</message> +</catalogue> diff --git a/src/java/org/apache/fop/accessibility/AccessibilityPreprocessor.java b/src/java/org/apache/fop/accessibility/AccessibilityPreprocessor.java new file mode 100644 index 000000000..1958b74a8 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/AccessibilityPreprocessor.java @@ -0,0 +1,95 @@ +/* + * 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.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.TransformerHandler; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +import org.apache.commons.io.output.ByteArrayOutputStream; + +import org.apache.fop.apps.FOUserAgent; +import org.apache.fop.util.TransformerDefaultHandler; + +/** + * This class prepares an XSL-FO document for accessibility. It adds a unique + * identifier to every applicable FO, then creates the structure tree, before + * handing the document over to the regular handler. + */ +class AccessibilityPreprocessor extends TransformerDefaultHandler { + + private final ByteArrayOutputStream enrichedFOBuffer = new ByteArrayOutputStream(); + + private final Transformer reduceFOTree; + + private final FOUserAgent userAgent; + + private final DefaultHandler fopHandler; + + public AccessibilityPreprocessor(TransformerHandler addPtr, Transformer reduceFOTree, + FOUserAgent userAgent, DefaultHandler fopHandler) { + super(addPtr); + this.reduceFOTree = reduceFOTree; + this.userAgent = userAgent; + this.fopHandler = fopHandler; + getTransformerHandler().setResult(new StreamResult(enrichedFOBuffer)); + } + + /** {@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(); + Source src = new StreamSource(new ByteArrayInputStream(enrichedFO)); + DOMResult res = new DOMResult(); + reduceFOTree.transform(src, res); + StructureTree structureTree = new StructureTree(); + NodeList pageSequences = res.getNode().getFirstChild().getChildNodes(); + for (int i = 0; i < pageSequences.getLength(); i++) { + structureTree.addPageSequenceStructure(pageSequences.item(i).getChildNodes()); + } + userAgent.setStructureTree(structureTree); + + SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); + saxParserFactory.setNamespaceAware(true); + saxParserFactory.setValidating(false); + SAXParser saxParser = saxParserFactory.newSAXParser(); + InputStream in = new ByteArrayInputStream(enrichedFO); + saxParser.parse(in, fopHandler); + } catch (Exception e) { + throw new SAXException(e); + } + } + +} diff --git a/src/java/org/apache/fop/accessibility/StructureTree.java b/src/java/org/apache/fop/accessibility/StructureTree.java new file mode 100644 index 000000000..a0fdaac21 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/StructureTree.java @@ -0,0 +1,102 @@ +/* + * 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.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +/** + * A reduced version of the document's FO tree, containing only its logical + * structure. Used by accessible output formats. + */ +public final class StructureTree { + + private final List pageSequenceStructures = new ArrayList(); + + /** + * Package-private default constructor. + */ + StructureTree() { } + + private static boolean flowOrStaticContentNodes(NodeList nodes) { + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node.getNodeType() != Node.ELEMENT_NODE) { + return false; + } + String name = node.getLocalName(); + if (!(name.equals("flow") || name.equals("static-content"))) { + return false; + } + } + return true; + } + + void addPageSequenceStructure(NodeList structureTree) { + assert flowOrStaticContentNodes(structureTree); + pageSequenceStructures.add(structureTree); + } + + /** + * Returns the list of nodes that are the children of the given page sequence. + * + * @param index index of the page sequence, 0-based + * @return its children nodes + */ + public NodeList getPageSequence(int index) { + return (NodeList) pageSequenceStructures.get(index); + } + + /** + * Returns an XML-like representation of the structure trees. + * <p> + * <strong>Note:</strong> use only for debugging purpose, as this method + * performs non-trivial operations. + * </p> + * @return a string representation of this object + */ + public String toString() { + try { + Transformer t = TransformerFactory.newInstance().newTransformer(); + Writer str = new StringWriter(); + for (Iterator iter = pageSequenceStructures.iterator(); iter.hasNext();) { + NodeList nodes = (NodeList) iter.next(); + for (int i = 0, c = nodes.getLength(); i < c; i++) { + t.transform(new DOMSource(nodes.item(i)), new StreamResult(str)); + } + } + return str.toString(); + } catch (Exception e) { + return e.toString(); + } + } + +} diff --git a/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java b/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java new file mode 100644 index 000000000..cd9aa011a --- /dev/null +++ b/src/java/org/apache/fop/accessibility/StructureTreeBuilder.java @@ -0,0 +1,95 @@ +/* + * 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.TransformerConfigurationException; +import javax.xml.transform.dom.DOMResult; +import javax.xml.transform.sax.SAXTransformerFactory; +import javax.xml.transform.sax.TransformerHandler; + +import org.xml.sax.ContentHandler; +import org.xml.sax.SAXException; + +import org.apache.fop.util.DelegatingContentHandler; + +/** + * Helper class that re-builds a structure tree from what is stored in an + * intermediate XML file (IF XML or Area Tree XML). + */ +public final class StructureTreeBuilder { + + private final SAXTransformerFactory factory; + + private final StructureTree structureTree = new StructureTree(); + + /** + * Creates a new instance. + * + * @param factory a factory internally used to build the structures of page + * sequences + */ + public StructureTreeBuilder(SAXTransformerFactory factory) { + this.factory = factory; + } + + /** + * Returns the structure tree that will result from the parsing. + * + * @return the structure tree built by this object + */ + public StructureTree getStructureTree() { + return structureTree; + } + + /** + * Returns a ContenHandler for parsing the structure of a new page sequence. + * It is assumed that page sequences are being parsed in the document order. + * + * @return a handler for parsing the <structure-tree> or + * <structureTree> element and its descendants + * @throws SAXException if there is an error when creating the handler + */ + public ContentHandler getHandlerForNextPageSequence() throws SAXException { + TransformerHandler structureTreeBuilder; + try { + structureTreeBuilder = factory.newTransformerHandler(); + } catch (TransformerConfigurationException e) { + throw new SAXException(e); + } + final DOMResult domResult = new DOMResult(); + structureTreeBuilder.setResult(domResult); + return new DelegatingContentHandler(structureTreeBuilder) { + + public void characters(char[] ch, int start, int length) throws SAXException { + /* + * There's no text node in the structure tree. This is just + * whitespace => ignore + */ + } + + public void endDocument() throws SAXException { + super.endDocument(); + structureTree.addPageSequenceStructure(domResult.getNode().getFirstChild() + .getChildNodes()); + } + }; + } + +} 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..b3984d426 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/addPtr.xsl @@ -0,0 +1,88 @@ +<?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 name="addPtr"> + <xsl:copy> + <xsl:apply-templates select="@*"/> + <xsl:attribute name="foi:ptr"> + <xsl:value-of select="generate-id()"/> + </xsl:attribute> + <xsl:apply-templates/> + </xsl:copy> + </xsl:template> + + <!-- Block-level Formatting Objects --> + <xsl:template match="fo:block|fo:block-container"> + <xsl:call-template name="addPtr"/> + </xsl:template> + + <!-- Inline-level Formatting Objects --> + <xsl:template match="fo:character|fo:inline|fo:inline-container"> + <xsl:call-template name="addPtr"/> + </xsl:template> + + <xsl:template match="fo:external-graphic|fo:instream-foreign-object"> + <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> + + <!-- Formatting Objects for Tables --> + <xsl:template match="fo:table-and-caption|fo:table-caption|fo:table"> + <xsl:call-template name="addPtr"/> + </xsl:template> + + <xsl:template match="fo:table-header|fo:table-footer|fo:table-body|fo:table-row|fo:table-cell"> + <xsl:call-template name="addPtr"/> + </xsl:template> + + <!-- Formatting Objects for Lists --> + <xsl:template match="fo:list-block|fo:list-item|fo:list-item-label|fo:list-item-body"> + <xsl:call-template name="addPtr"/> + </xsl:template> + + <!-- Dynamic Effects: Link and Multi Formatting Objects --> + <xsl:template match="fo:basic-link"> + <xsl:call-template name="addPtr"/> + </xsl:template> + + <!-- Out-of-Line Formatting Objects --> + <xsl:template match="fo:float|fo:footnote|fo:footnote-body"> + <xsl:call-template name="addPtr"/> + </xsl:template> + + <!-- Other Formatting Objects --> + <xsl:template match="fo:wrapper|fo:marker"> + <xsl:call-template name="addPtr"/> + </xsl:template> + + + <xsl:template match="@*|node()"> + <xsl:copy> + <xsl:apply-templates select="@*|node()"/> + </xsl:copy> + </xsl:template> + +</xsl:stylesheet> 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..8e9bcfc13 --- /dev/null +++ b/src/java/org/apache/fop/accessibility/reduceFOTree.xsl @@ -0,0 +1,100 @@ +<?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:fox="http://xmlgraphics.apache.org/fop/extensions" + xmlns:foi="http://xmlgraphics.apache.org/fop/internal"> + + <xsl:output method="xml" indent="no"/> + + <xsl:template name="copy"> + <xsl:copy> + <xsl:apply-templates select="@*|node()"/> + </xsl:copy> + </xsl:template> + + <!-- Declarations and Pagination and Layout Formatting Objects --> + <xsl:template match="fo:root|fo:page-sequence|fo:static-content|fo:flow"> + <xsl:call-template name="copy"/> + </xsl:template> + + <!-- Block-level Formatting Objects --> + <xsl:template match="fo:block|fo:block-container"> + <xsl:call-template name="copy"/> + </xsl:template> + + <!-- Inline-level Formatting Objects --> + <xsl:template match="fo:character|fo:inline|fo:inline-container"> + <xsl:call-template name="copy"/> + </xsl:template> + + <xsl:template match="fo:external-graphic|fo:instream-foreign-object"> + <xsl:call-template name="copy"/> + </xsl:template> + + <xsl:template match="fo:page-number|fo:page-number-citation|fo:page-number-citation-last"> + <xsl:call-template name="copy"/> + </xsl:template> + + <!-- Formatting Objects for Tables --> + <xsl:template match="fo:table-and-caption|fo:table-caption|fo:table"> + <xsl:call-template name="copy"/> + </xsl:template> + + <xsl:template match="fo:table-header|fo:table-footer|fo:table-body|fo:table-row|fo:table-cell"> + <xsl:call-template name="copy"/> + </xsl:template> + + <!-- Formatting Objects for Lists --> + <xsl:template match="fo:list-block|fo:list-item|fo:list-item-label|fo:list-item-body"> + <xsl:call-template name="copy"/> + </xsl:template> + + <!-- Dynamic Effects: Link and Multi Formatting Objects --> + <xsl:template match="fo:basic-link"> + <xsl:call-template name="copy"/> + </xsl:template> + + <!-- Out-of-Line Formatting Objects --> + <xsl:template match="fo:float|fo:footnote|fo:footnote-body"> + <xsl:call-template name="copy"/> + </xsl:template> + + <!-- Other Formatting Objects --> + <xsl:template match="fo:wrapper|fo:marker"> + <xsl:call-template name="copy"/> + </xsl:template> + + + <!-- Discard descendants of fo:leader --> + <xsl:template match="fo:leader"/> + + + <!-- Keep foi:ptr and fox:alt-text attributes, discard everything else --> + <xsl:template match="@foi:ptr|@fox:alt-text"> + <xsl:copy-of select="."/> + </xsl:template> + + <xsl:template match="@*"/> + + + <!-- Discard text --> + <xsl:template match="text()"/> + +</xsl:stylesheet> diff --git a/src/java/org/apache/fop/apps/FOUserAgent.java b/src/java/org/apache/fop/apps/FOUserAgent.java index 8ee472dcf..0a65203cc 100644 --- a/src/java/org/apache/fop/apps/FOUserAgent.java +++ b/src/java/org/apache/fop/apps/FOUserAgent.java @@ -37,6 +37,8 @@ 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.Accessibility; +import org.apache.fop.accessibility.StructureTree; import org.apache.fop.events.DefaultEventBroadcaster; import org.apache.fop.events.Event; import org.apache.fop.events.EventBroadcaster; @@ -99,6 +101,8 @@ public class FOUserAgent { private boolean conserveMemoryPolicy = false; private EventBroadcaster eventBroadcaster = new FOPEventBroadcaster(); + private StructureTree structureTree; + /** Producer: Metadata element for the system/software that produces * the document. (Some renderers can store this in the document.) */ @@ -152,6 +156,7 @@ public class FOUserAgent { this.factory = factory; setBaseURL(factory.getBaseURL()); setTargetResolution(factory.getTargetResolution()); + setAccessibility(factory.isAccessibilityEnabled()); } /** @return the associated FopFactory instance */ @@ -196,6 +201,7 @@ public class FOUserAgent { return rendererOverride; } + /** * Sets an explicit FOEventHandler instance which overrides the one * defined by the render type setting. @@ -642,5 +648,49 @@ public class FOUserAgent { this.conserveMemoryPolicy = conserveMemoryPolicy; } + /** + * Activates accessibility (for output formats that support it). + * + * @param accessibility <code>true</code> to enable accessibility support + */ + public void setAccessibility(boolean accessibility) { + if (accessibility) { + getRendererOptions().put(Accessibility.ACCESSIBILITY, Boolean.TRUE); + } + } + + /** + * Check if accessibility is enabled. + * @return true if accessibility is enabled + */ + public boolean isAccessibilityEnabled() { + Boolean enabled = (Boolean)this.getRendererOptions().get(Accessibility.ACCESSIBILITY); + if (enabled != null) { + return enabled.booleanValue(); + } else { + return false; + } + } + + /** + * Sets the document's structure tree, for use by accessible output formats. + * + * @param structureTree a simplified version of the FO tree, retaining only + * its logical structure + */ + public void setStructureTree(StructureTree structureTree) { + this.structureTree = structureTree; + } + + /** + * Returns the document's structure tree, for use by accessible output + * formats. + * + * @return a simplified version of the FO tree, retaining only its logical + * structure + */ + public StructureTree getStructureTree() { + return this.structureTree; + } } diff --git a/src/java/org/apache/fop/apps/Fop.java b/src/java/org/apache/fop/apps/Fop.java index 0527ea290..07fd4c0a4 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.Accessibility; 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 Accessibility.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..907895c99 100644 --- a/src/java/org/apache/fop/apps/FopFactory.java +++ b/src/java/org/apache/fop/apps/FopFactory.java @@ -100,6 +100,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; @@ -185,6 +190,19 @@ public class FopFactory implements ImageContext { } /** + * Sets accessibility support. + * + * @param value <code>true</code> to enable accessibility, <code>false</code> otherwise + */ + void setAccessibility(boolean value) { + this.accessibility = value; + } + + boolean isAccessibilityEnabled() { + return accessibility; + } + + /** * 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..ffffda7f4 100644 --- a/src/java/org/apache/fop/area/AreaTreeParser.java +++ b/src/java/org/apache/fop/area/AreaTreeParser.java @@ -39,8 +39,6 @@ import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; import org.xml.sax.Attributes; @@ -50,12 +48,17 @@ import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.xml.sax.helpers.DefaultHandler; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageInfo; import org.apache.xmlgraphics.image.loader.ImageManager; import org.apache.xmlgraphics.image.loader.ImageSessionContext; import org.apache.xmlgraphics.util.QName; +import org.apache.fop.accessibility.AccessibilityEventProducer; +import org.apache.fop.accessibility.StructureTreeBuilder; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.area.Trait.Background; import org.apache.fop.area.Trait.InternalLink; @@ -84,6 +87,8 @@ 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.DelegatingContentHandler; +import org.apache.fop.util.XMLConstants; import org.apache.fop.util.XMLUtil; /** @@ -156,6 +161,26 @@ public class AreaTreeParser { private Locator locator; + private StructureTreeBuilder structureTreeBuilder; + + private ContentHandler structureTreeBuilderWrapper; + + private Attributes pageSequenceAttributes; + + private final class StructureTreeBuilderWrapper extends DelegatingContentHandler { + + private StructureTreeBuilderWrapper() + throws SAXException { + super(structureTreeBuilder.getHandlerForNextPageSequence()); + } + + public void endDocument() throws SAXException { + super.endDocument(); + startAreaTreeElement("pageSequence", pageSequenceAttributes); + pageSequenceAttributes = null; + } + } + public Handler(AreaTreeModel treeModel, FOUserAgent userAgent, ElementMappingRegistry elementMappingRegistry) { this.treeModel = treeModel; @@ -192,6 +217,11 @@ public class AreaTreeParser { makers.put("bookmarkTree", new BookmarkTreeMaker()); makers.put("bookmark", new BookmarkMaker()); makers.put("destination", new DestinationMaker()); + + if (userAgent.isAccessibilityEnabled()) { + structureTreeBuilder = new StructureTreeBuilder(tFactory); + userAgent.setStructureTree(structureTreeBuilder.getStructureTree()); + } } private Area findAreaType(Class clazz) { @@ -265,19 +295,35 @@ public class AreaTreeParser { delegate.startDocument(); delegate.startElement(uri, localName, qName, attributes); } else { - lastAttributes = new AttributesImpl(attributes); boolean handled = true; if ("".equals(uri)) { - Maker maker = (Maker)makers.get(localName); - content.clear(); - ignoreCharacters = true; - if (maker != null) { - ignoreCharacters = maker.ignoreCharacters(); - maker.startElement(attributes); - } else if ("extension-attachments".equals(localName)) { - //TODO implement me + if (localName.equals("pageSequence") && userAgent.isAccessibilityEnabled()) { + structureTreeBuilderWrapper = new StructureTreeBuilderWrapper(); + pageSequenceAttributes = new AttributesImpl(attributes); + } else if (localName.equals("structureTree")) { + if (userAgent.isAccessibilityEnabled()) { + delegate = structureTreeBuilderWrapper; + } else { + /* Delegate to a handler that does nothing */ + delegate = new DefaultHandler(); + } + delegateStack.push(qName); + delegate.startDocument(); + delegate.startElement(uri, localName, qName, attributes); } else { - handled = false; + if (pageSequenceAttributes != null) { + /* + * This means that no structure-element tag was + * found in the XML, otherwise a + * StructureTreeBuilderWrapper object would have + * been created, which would have reset the + * pageSequenceAttributes field. + */ + AccessibilityEventProducer.Provider + .get(userAgent.getEventBroadcaster()) + .noStructureTreeInXML(this); + } + handled = startAreaTreeElement(localName, attributes); } } else { ContentHandlerFactoryRegistry registry @@ -304,6 +350,23 @@ public class AreaTreeParser { } } + private boolean startAreaTreeElement(String localName, Attributes attributes) + throws SAXException { + lastAttributes = new AttributesImpl(attributes); + Maker maker = (Maker)makers.get(localName); + content.clear(); + ignoreCharacters = true; + if (maker != null) { + ignoreCharacters = maker.ignoreCharacters(); + maker.startElement(attributes); + } else if ("extension-attachments".equals(localName)) { + //TODO implement me + } else { + return false; + } + return true; + } + /** {@inheritDoc} */ public void endElement(String uri, String localName, String qName) throws SAXException { if (delegate != null) { @@ -700,6 +763,7 @@ public class AreaTreeParser { setTraits(attributes, ip, SUBSET_BOX); setTraits(attributes, ip, SUBSET_COLOR); setTraits(attributes, ip, SUBSET_LINK); + setPtr(ip, attributes); Area parent = (Area)areaStack.peek(); parent.addChildArea(ip); areaStack.push(ip); @@ -748,6 +812,7 @@ public class AreaTreeParser { "tlsadjust", 0)); text.setTextWordSpaceAdjust(XMLUtil.getAttributeAsInt(attributes, "twsadjust", 0)); + setPtr(text, attributes); Area parent = (Area)areaStack.peek(); parent.addChildArea(text); areaStack.push(text); @@ -840,6 +905,7 @@ public class AreaTreeParser { viewport.setContentPosition(XMLUtil.getAttributeAsRectangle2D(attributes, "pos")); viewport.setClip(XMLUtil.getAttributeAsBoolean(attributes, "clip", false)); viewport.setOffset(XMLUtil.getAttributeAsInt(attributes, "offset", 0)); + setPtr(viewport, attributes); Area parent = (Area)areaStack.peek(); parent.addChildArea(viewport); areaStack.push(viewport); @@ -858,6 +924,7 @@ public class AreaTreeParser { transferForeignObjects(attributes, image); setAreaAttributes(attributes, image); setTraits(attributes, image, SUBSET_COMMON); + setPtr(image, attributes); getCurrentViewport().setContent(image); } } @@ -991,10 +1058,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 +1200,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)); @@ -1142,6 +1209,13 @@ public class AreaTreeParser { } } + private void setPtr(Area area, Attributes attributes) { + String ptr = attributes.getValue("ptr"); + if (ptr != null) { + area.addTrait(Trait.PTR, ptr); + } + } + /** {@inheritDoc} */ public void characters(char[] ch, int start, int length) throws SAXException { if (delegate != null) { 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 8f396c04a..011e84008 100644 --- a/src/java/org/apache/fop/cli/CommandLineOptions.java +++ b/src/java/org/apache/fop/cli/CommandLineOptions.java @@ -36,6 +36,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.fop.Version; +import org.apache.fop.accessibility.Accessibility; import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.apps.FopFactory; @@ -342,6 +343,8 @@ public class CommandLineOptions { i = i + parseAreaTreeOption(args, i); } else if (args[i].equals("-if")) { i = i + parseIntermediateFormatOption(args, i); + } else if (args[i].equals("-a")) { + this.renderingOptions.put(Accessibility.ACCESSIBILITY, Boolean.TRUE); } else if (args[i].equals("-v")) { /* Currently just print the version */ printVersion(); @@ -1155,6 +1158,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/events/EventFormatter.xml b/src/java/org/apache/fop/events/EventFormatter.xml index 84e3883f8..341dd1395 100644 --- a/src/java/org/apache/fop/events/EventFormatter.xml +++ b/src/java/org/apache/fop/events/EventFormatter.xml @@ -107,4 +107,5 @@ Any reference to it will be considered a reference to the first occurrence in th <message key="org.apache.fop.fonts.FontEventAdapter.fontSubstituted">Font "{requested}" not found. Substituting with "{effective}".</message> <message key="org.apache.fop.fonts.FontEventAdapter.fontLoadingErrorAtAutoDetection">Unable to load font file: {fontURL}.[ Reason: {e}]</message> <message key="org.apache.fop.fonts.FontEventAdapter.glyphNotAvailable">Glyph "{ch}" (0x{ch,hex}[, {ch,glyph-name}]) not available in font "{fontName}".</message> + <message key="org.apache.fop.fo.FOValidationEventProducer.altTextMissing">Alternate text is missing on {foElement}.{{locator}}</message> </catalogue> 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 9f18ee6ee..bbe3ce3a3 100644 --- a/src/java/org/apache/fop/fo/FONode.java +++ b/src/java/org/apache/fop/fo/FONode.java @@ -36,6 +36,7 @@ import org.apache.fop.apps.FOPException; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.extensions.ExtensionAttachment; import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.extensions.InternalElementMapping; import org.apache.fop.fo.extensions.svg.SVGElementMapping; import org.apache.fop.fo.pagination.Root; import org.apache.fop.util.CharUtilities; @@ -415,6 +416,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 de6ffca93..07fb20fd8 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/FOValidationEventProducer.java b/src/java/org/apache/fop/fo/FOValidationEventProducer.java index 889bf706a..ff005b1b4 100644 --- a/src/java/org/apache/fop/fo/FOValidationEventProducer.java +++ b/src/java/org/apache/fop/fo/FOValidationEventProducer.java @@ -36,7 +36,9 @@ public interface FOValidationEventProducer extends EventProducer { /** * Provider class for the event producer. */ - class Provider { + final class Provider { + + private Provider() { } /** * Returns an event producer. @@ -354,4 +356,13 @@ public interface FOValidationEventProducer extends EventProducer { void unknownFormattingObject(Object source, String elementName, QName offendingNode, Locator loc); + /** + * Alternate text is missing for a graphic element. + * + * @param source the event source + * @param foElement name of the element (external-graphic or instream-foreign-object) + * @param loc the location of the error or null + * @event.severity WARN + */ + void altTextMissing(Object source, String foElement, Locator loc); } diff --git a/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java b/src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java index 927bed0f7..f9a556167 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-text"); } /** 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..7704c8de7 --- /dev/null +++ b/src/java/org/apache/fop/fo/extensions/InternalElementMapping.java @@ -0,0 +1,73 @@ +/* + * 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; + +/** + * 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 PROPERTY_ATTRIBUTES = new java.util.HashSet(); + + static { + //These are FOP's extension properties for accessibility + PROPERTY_ATTRIBUTES.add("ptr"); + } + + /** + * Constructor. + */ + public InternalElementMapping() { + namespaceURI = URI; + } + + /** + * Initialize the data structures. + */ + protected void initialize() { + if (foObjs == null) { + foObjs = new HashMap(); + } + } + + /** {@inheritDoc} */ + 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 PROPERTY_ATTRIBUTES.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..eeb06c1df 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(); @@ -102,6 +106,12 @@ public abstract class AbstractGraphics extends FObj implements GraphicsPropertie scaling = pList.get(PR_SCALING).getEnum(); textAlign = pList.get(PR_TEXT_ALIGN).getEnum(); width = pList.get(PR_WIDTH).getLength(); + if (getUserAgent().isAccessibilityEnabled()) { + String altText = pList.get(PR_X_ALT_TEXT).getString(); + if (altText.equals("")) { + getFOValidationEventProducer().altTextMissing(this, getLocalName(), getLocator()); + } + } } /** @@ -207,6 +217,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/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 3bdd8ad43..d7453a399 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 03f272660..f793bb3bb 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; @@ -506,12 +509,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/PDFDocument.java b/src/java/org/apache/fop/pdf/PDFDocument.java index 46effdfd6..f0a777bdd 100644 --- a/src/java/org/apache/fop/pdf/PDFDocument.java +++ b/src/java/org/apache/fop/pdf/PDFDocument.java @@ -355,6 +355,25 @@ public class PDFDocument { } /** + * Makes sure a Lang entry has been set on the document catalog, setting it + * to a default value if necessary. When accessibility is enabled the + * language must be specified for any text element in the document. + */ + public void enforceLanguageOnRoot() { + if (root.getLanguage() == null) { + String fallbackLanguage; + if (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"; + } + root.setLanguage(fallbackLanguage); + } + } + + /** * Get the {@link PDFInfo} object for this document. * * @return the {@link PDFInfo} object diff --git a/src/java/org/apache/fop/pdf/PDFFactory.java b/src/java/org/apache/fop/pdf/PDFFactory.java index a8d7a0702..bf3399b09 100644 --- a/src/java/org/apache/fop/pdf/PDFFactory.java +++ b/src/java/org/apache/fop/pdf/PDFFactory.java @@ -185,6 +185,10 @@ public class PDFFactory { public PDFPage makePage(PDFResources resources, int pageIndex, Rectangle2D mediaBox, Rectangle2D cropBox, Rectangle2D bleedBox, Rectangle2D trimBox) { + /* + * create a PDFPage with the next object number, the given + * resources, contents and dimensions + */ PDFPage page = new PDFPage(resources, pageIndex, mediaBox, cropBox, bleedBox, trimBox); getDocument().assignObjectNumber(page); @@ -883,6 +887,35 @@ public class PDFFactory { } /** + * Creates and returns a StructTreeRoot object. Used for accessibility. + * @param parentTree the value of the ParenTree entry + * @return structure Tree Root element + */ + public PDFStructTreeRoot makeStructTreeRoot(PDFParentTree parentTree) { + PDFStructTreeRoot structTreeRoot = new PDFStructTreeRoot(parentTree); + getDocument().assignObjectNumber(structTreeRoot); + getDocument().addTrailerObject(structTreeRoot); + getDocument().getRoot().setStructTreeRoot(structTreeRoot); + return structTreeRoot; + } + + /** + * Creates and returns a StructElem object. + * + * @param structureType the structure type of the new element (value for the + * S entry) + * @param parent the parent of the new structure element in the structure + * hierarchy + * @return the newly created element + */ + public PDFStructElem makeStructureElement(PDFName structureType, PDFObject parent) { + PDFStructElem structElem = new PDFStructElem(parent, structureType); + getDocument().assignObjectNumber(structElem); + getDocument().addTrailerObject(structElem); + return structElem; + } + + /** * 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..934932648 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,16 @@ public class PDFLink extends PDFObject { this.action = action; } + + /** + * Sets the value of the StructParent entry for this link. + * + * @param structParent key in the structure parent tree + */ + public void setStructParent(int structParent) { + this.structParent = new Integer(structParent); + } + /** * {@inheritDoc} */ @@ -87,6 +98,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/PDFNumsArray.java b/src/java/org/apache/fop/pdf/PDFNumsArray.java index e1c38dd48..6db7b02ac 100644 --- a/src/java/org/apache/fop/pdf/PDFNumsArray.java +++ b/src/java/org/apache/fop/pdf/PDFNumsArray.java @@ -57,8 +57,26 @@ public class PDFNumsArray extends PDFObject { * @param key the key of the value to set * @param obj the new value */ + public void put(Integer key, Object obj) { + this.map.put(key, obj); + } + + /** + * Sets an entry. + * @param key the key of the value to set + * @param obj the new value + */ public void put(int key, Object obj) { - this.map.put(new Integer(key), obj); + put(new Integer(key), obj); + } + + /** + * Gets an entry. + * @param key the key of requested value + * @return the requested value + */ + public Object get(Integer key) { + return this.map.get(key); } /** @@ -67,7 +85,7 @@ public class PDFNumsArray extends PDFObject { * @return the requested value */ public Object get(int key) { - return this.map.get(new Integer(key)); + return get(new Integer(key)); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/pdf/PDFPage.java b/src/java/org/apache/fop/pdf/PDFPage.java index 4fa3b0a09..7a5365761 100644 --- a/src/java/org/apache/fop/pdf/PDFPage.java +++ b/src/java/org/apache/fop/pdf/PDFPage.java @@ -154,4 +154,33 @@ 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); + //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. + setTabs(new PDFName("S")); + } + + /** + * Returns the value of the StructParents entry. + * + * @return the StructParents value, <code>null</code> if the entry has not been set + */ + public Integer getStructParents() { + return (Integer) get("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..7876bbc0c --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFParentTree.java @@ -0,0 +1,44 @@ +/* + * 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 { + + /** + * Returns the number tree corresponding to this parent tree. + * + * @return the number tree + */ + 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..f71841005 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,7 +59,7 @@ 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 @@ -68,6 +71,12 @@ public class PDFRoot extends PDFDictionary { 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..4fb8cbcd5 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFStructElem.java @@ -0,0 +1,159 @@ +/* + * 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 PDFStructElem parentElement; + + /** + * Creates a new structure element. + * + * @param parent parent of this element + * @param structureType the structure type of this element + */ + PDFStructElem(PDFObject parent, PDFName structureType) { + if (parent instanceof PDFStructElem) { + parentElement = (PDFStructElem) parent; + } + put("Type", new PDFName("StructElem")); + put("S", structureType); + setParent(parent); + } + + /** + * Returns the parent of this structure element. + * + * @return the parent, <code>null</code> if the parent is not a structure + * element (i.e., is the structure tree root) + */ + public PDFStructElem getParentStructElem() { + return parentElement; + } + + /** {@inheritDoc} */ + public void setParent(PDFObject parent) { + if (parent != null) { + put("P", new PDFReference(parent)); + } + } + + /** + * Returns the kids of this structure element. + * + * @return the value of the K entry + */ + private PDFArray getKids() { + return (PDFArray) get("K"); + } + + /** + * Add a kid to this structure element. This element will then add itself to + * its parent structure element if it has not already, and so will the + * parent, and so on. + * + * @param kid element to be added + */ + public void addKid(PDFObject kid) { + PDFArray kids = getKids(); + if (kids == null) { + kids = new PDFArray(); + put("K", kids); + } + kids.add(kid); + joinHierarchy(); + } + + private boolean containsKid(PDFObject kid) { + PDFArray kids = getKids(); + return kids != null && kids.contains(kid); + } + + private void joinHierarchy() { + if (parentElement != null && !parentElement.containsKid(this)) { + parentElement.addKid(this); + } + } + + /** + * Sets the given mcid as the kid of this structure element. This element + * will then add itself to its parent structure element if it has not + * already, and so will the parent, and so on. + * + * @param mcid mcid of the marked-content sequence corresponding to this + * structure element's kid + */ + public void setMCIDKid(int mcid) { + put("K", mcid); + joinHierarchy(); + } + + /** + * Sets the page reference of this structure element. + * + * @param page value for the Pg entry + */ + public void setPage(PDFPage page) { + put("Pg", page); + } + + /** + * Returns the structure type of this structure element. + * + * @return the value of the S entry + */ + 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") + */ + private void setLanguage(String language) { + put("Lang", language); + } + + /** + * Sets the language of this structure element. + * + * @param language a value for the Lang entry + */ + public void setLanguage(Locale language) { + setLanguage(XMLUtil.toRFC3066(language)); + } + + /** + * Returns the language of this structure element. + * + * @return the value of the Lang entry (<code>null</code> 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..5b3f63106 --- /dev/null +++ b/src/java/org/apache/fop/pdf/PDFStructTreeRoot.java @@ -0,0 +1,55 @@ +/* + * 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 { + + /** + * Creates a new /StructTreeRoot dictionary. + * + * @param parentTree the value of the ParenTree entry + */ + PDFStructTreeRoot(PDFParentTree parentTree) { + put("Type", new PDFName("StructTreeRoot")); + put("K", new PDFArray()); + put("ParentTree", parentTree); + } + + /** + * Returns the children element of this StructTreeRoot. + * + * @return the value of the K entry + */ + public PDFArray getKids() { + return (PDFArray)get("K"); + } + + /** + * Adds the given object to the array of kids. + * + * @param kid an object to be added to the K entry + */ + 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..e1bc10440 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; /** @@ -29,7 +30,7 @@ import org.apache.fop.apps.FOUserAgent; public abstract class AbstractRendererConfigurator extends AbstractConfigurator { private static final String TYPE = "renderer"; - + /** * Default constructor * @param userAgent user agent @@ -55,7 +56,7 @@ public abstract class AbstractRendererConfigurator extends AbstractConfigurator protected Configuration getRendererConfig(String mimeType) { return super.getConfig(mimeType); } - + /** * {@inheritDoc} */ 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/intermediate/IFConstants.java b/src/java/org/apache/fop/render/intermediate/IFConstants.java index e7f7e1a00..fa234b4db 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"; + /** Parent element of the logical structure tree. */ + 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..804b353c1 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,46 @@ 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; + } + } diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java index 38344c364..da5d8a623 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; @@ -34,7 +35,6 @@ import javax.xml.transform.sax.SAXTransformerFactory; import org.w3c.dom.DOMImplementation; import org.w3c.dom.Document; - import org.xml.sax.Attributes; import org.xml.sax.ContentHandler; import org.xml.sax.SAXException; @@ -46,6 +46,8 @@ import org.apache.commons.logging.LogFactory; import org.apache.xmlgraphics.util.QName; +import org.apache.fop.accessibility.AccessibilityEventProducer; +import org.apache.fop.accessibility.StructureTreeBuilder; import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fo.ElementMapping; import org.apache.fop.fo.ElementMappingRegistry; @@ -59,6 +61,7 @@ import org.apache.fop.util.ContentHandlerFactory; import org.apache.fop.util.ContentHandlerFactoryRegistry; import org.apache.fop.util.DOMBuilderContentHandlerFactory; import org.apache.fop.util.DefaultErrorListener; +import org.apache.fop.util.DelegatingContentHandler; import org.apache.fop.util.XMLUtil; /** @@ -73,6 +76,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 @@ -140,6 +152,26 @@ public class IFParser implements IFConstants { private ContentHandler navParser; + private StructureTreeBuilder structureTreeBuilder; + + private ContentHandler structureTreeBuilderWrapper; + + private Attributes pageSequenceAttributes; + + private final class StructureTreeBuilderWrapper extends DelegatingContentHandler { + + private StructureTreeBuilderWrapper() + throws SAXException { + super(structureTreeBuilder.getHandlerForNextPageSequence()); + } + + public void endDocument() throws SAXException { + super.endDocument(); + startIFElement(EL_PAGE_SEQUENCE, pageSequenceAttributes); + pageSequenceAttributes = null; + } + } + public Handler(IFDocumentHandler documentHandler, FOUserAgent userAgent, ElementMappingRegistry elementMappingRegistry) { this.documentHandler = documentHandler; @@ -163,6 +195,11 @@ public class IFParser implements IFConstants { elementHandlers.put(EL_LINE, new LineHandler()); elementHandlers.put(EL_BORDER_RECT, new BorderRectHandler()); elementHandlers.put(EL_IMAGE, new ImageHandler()); + + if (userAgent.isAccessibilityEnabled()) { + structureTreeBuilder = new StructureTreeBuilder(tFactory); + userAgent.setStructureTree(structureTreeBuilder.getStructureTree()); + } } private void establishForeignAttributes(Map foreignAttributes) { @@ -173,31 +210,50 @@ 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 { boolean handled = true; if (NAMESPACE.equals(uri)) { - lastAttributes = new AttributesImpl(attributes); - ElementHandler elementHandler = (ElementHandler)elementHandlers.get(localName); - content.setLength(0); - ignoreCharacters = true; - if (elementHandler != null) { - ignoreCharacters = elementHandler.ignoreCharacters(); - try { - elementHandler.startElement(attributes); - } catch (IFException ife) { - handleIFException(ife); + if (localName.equals(EL_PAGE_SEQUENCE) && userAgent.isAccessibilityEnabled()) { + pageSequenceAttributes = new AttributesImpl(attributes); + structureTreeBuilderWrapper = new StructureTreeBuilderWrapper(); + } else if (localName.equals(EL_STRUCTURE_TREE)) { + if (userAgent.isAccessibilityEnabled()) { + delegate = structureTreeBuilderWrapper; + } else { + /* Delegate to a handler that does nothing */ + delegate = new DefaultHandler(); } - } else if ("extension-attachments".equals(localName)) { - //TODO implement me + delegateDepth++; + delegate.startDocument(); + delegate.startElement(uri, localName, qName, attributes); } else { - handled = false; + if (pageSequenceAttributes != null) { + /* + * This means that no structure-element tag was + * found in the XML, otherwise a + * StructureTreeBuilderWrapper object would have + * been created, which would have reset the + * pageSequenceAttributes field. + */ + AccessibilityEventProducer.Provider + .get(userAgent.getEventBroadcaster()) + .noStructureTreeInXML(this); + } + handled = startIFElement(localName, attributes); } } else if (DocumentNavigationExtensionConstants.NAMESPACE.equals(uri)) { if (this.navParser == null) { @@ -241,6 +297,25 @@ public class IFParser implements IFConstants { } } + private boolean startIFElement(String localName, Attributes attributes) + throws SAXException { + lastAttributes = new AttributesImpl(attributes); + ElementHandler elementHandler = (ElementHandler)elementHandlers.get(localName); + content.setLength(0); + ignoreCharacters = true; + if (elementHandler != null) { + ignoreCharacters = elementHandler.ignoreCharacters(); + try { + elementHandler.startElement(attributes); + } catch (IFException ife) { + handleIFException(ife); + } + return true; + } else { + return false; + } + } + private void handleIFException(IFException ife) throws SAXException { if (ife.getCause() instanceof SAXException) { //unwrap @@ -352,6 +427,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 +440,7 @@ public class IFParser implements IFConstants { public void endElement() throws IFException { documentHandler.endPageSequence(); + documentHandler.getContext().setLanguage(null); } } @@ -484,7 +565,9 @@ public class IFParser implements IFConstants { s = lastAttributes.getValue("word-spacing"); int wordSpacing = (s != null ? Integer.parseInt(s) : 0); int[] dx = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx"); + setStructurePointer(lastAttributes); painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString()); + resetStructurePointer(); } public boolean ignoreCharacters() { @@ -579,6 +662,7 @@ public class IFParser implements IFConstants { int height = Integer.parseInt(lastAttributes.getValue("height")); Map foreignAttributes = getForeignAttributes(lastAttributes); establishForeignAttributes(foreignAttributes); + setStructurePointer(lastAttributes); if (foreignObject != null) { painter.drawImage(foreignObject, new Rectangle(x, y, width, height)); @@ -592,6 +676,7 @@ public class IFParser implements IFConstants { painter.drawImage(uri, new Rectangle(x, y, width, height)); } resetForeignAttributes(); + resetStructurePointer(); inForeignObject = false; } @@ -632,11 +717,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) { @@ -649,6 +730,13 @@ public class IFParser implements IFConstants { return foreignAttributes; } + private void setStructurePointer(Attributes attributes) { + String ptr = attributes.getValue("ptr"); + if (ptr != null && ptr.length() > 0) { + establishStructurePointer(ptr); + } + } + /** {@inheritDoc} */ public void characters(char[] ch, int start, int length) throws SAXException { if (delegate != null) { diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index 059506dec..13ac401a7 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.setStructurePointer(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..2401b1202 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -27,16 +27,19 @@ import java.awt.Rectangle; import java.awt.geom.AffineTransform; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; 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; import org.apache.xmlgraphics.util.QName; import org.apache.xmlgraphics.util.XMLizable; +import org.apache.fop.accessibility.StructureTree; import org.apache.fop.fonts.FontInfo; import org.apache.fop.render.PrintRendererConfigurator; import org.apache.fop.render.RenderingContext; @@ -60,6 +63,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler implements IFConstants, IFPainter, IFDocumentNavigationHandler { private IFDocumentHandler mimicHandler; + private int pageSequenceIndex; // used for accessibility /** Holds the intermediate format state */ private IFState state; @@ -210,8 +214,23 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (id != null) { atts.addAttribute(XML_NAMESPACE, "id", "xml:id", XMLUtil.CDATA, id); } + Locale lang = getContext().getLanguage(); + if (lang != null) { + atts.addAttribute(XML_NAMESPACE, "lang", "xml:lang", XMLUtil.CDATA, + XMLUtil.toRFC3066(lang)); + } addForeignAttributes(atts); handler.startElement(EL_PAGE_SEQUENCE, atts); + if (this.getUserAgent().isAccessibilityEnabled()) { + StructureTree structureTree = getUserAgent().getStructureTree(); + handler.startElement(EL_STRUCTURE_TREE); // add structure tree + NodeList nodes = structureTree.getPageSequence(pageSequenceIndex++); + 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); } @@ -392,13 +411,14 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); + addStructurePointerAttribute(atts); handler.element(EL_IMAGE, atts); } catch (SAXException e) { throw new IFException("SAX error in startGroup()", e); } } - private void addForeignAttributes(AttributesImpl atts) { + private void addForeignAttributes(AttributesImpl atts) throws SAXException { Map foreignAttributes = getContext().getForeignAttributes(); if (!foreignAttributes.isEmpty()) { Iterator iter = foreignAttributes.entrySet().iterator(); @@ -418,6 +438,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); + addStructurePointerAttribute(atts); handler.startElement(EL_IMAGE, atts); new DOM2SAX(handler).writeDocument(doc, true); handler.endElement(EL_IMAGE); @@ -531,6 +552,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (dx != null) { addAttribute(atts, "dx", IFUtil.toString(dx)); } + addStructurePointerAttribute(atts); handler.startElement(EL_TEXT, atts); char[] chars = text.toCharArray(); handler.characters(chars, 0, chars.length); @@ -617,7 +639,8 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } private void addAttribute(AttributesImpl atts, - org.apache.xmlgraphics.util.QName attribute, String value) { + org.apache.xmlgraphics.util.QName attribute, String value) throws SAXException { + handler.startPrefixMapping(attribute.getPrefix(), attribute.getNamespaceURI()); XMLUtil.addAttribute(atts, attribute, value); } @@ -625,6 +648,13 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler XMLUtil.addAttribute(atts, localName, value); } + private void addStructurePointerAttribute(AttributesImpl atts) { + String ptr = getContext().getStructurePointer(); + if (ptr != null) { + addAttribute(atts, "ptr", ptr); + } + } + // ---=== IFDocumentNavigationHandler ===--- private Map incompleteActions = new java.util.HashMap(); @@ -696,6 +726,9 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler AttributesImpl atts = new AttributesImpl(); atts.addAttribute(null, "rect", "rect", XMLConstants.CDATA, IFUtil.toString(link.getTargetRect())); + if (getUserAgent().isAccessibilityEnabled()) { + addAttribute(atts, "ptr", link.getAction().getStructurePointer()); + } try { handler.startElement(DocumentNavigationExtensionConstants.LINK, atts); serializeXMLizable(link.getAction()); 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..340b2e068 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 structurePointer; /** * Sets an ID to make the action referencable. @@ -45,6 +46,22 @@ public abstract class AbstractAction implements XMLizable { } /** + * Sets the structure element corresponding to this action. + * @param structurePointer a reference to the structure element + */ + public void setStructurePointer(String structurePointer) { + this.structurePointer = structurePointer; + } + + /** + * Returns the structure element corresponding to this action. + * @return the reference to the structure element + */ + public String getStructurePointer() { + return structurePointer; + } + + /** * 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/intermediate/extensions/DocumentNavigationHandler.java b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java index 376130838..5ca480f4a 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java @@ -48,6 +48,8 @@ public class DocumentNavigationHandler extends DefaultHandler private IFDocumentNavigationHandler navHandler; + private String structurePointer; + /** * Main constructor. * @param navHandler the navigation handler that will receive the events @@ -96,6 +98,7 @@ public class DocumentNavigationHandler extends DefaultHandler throw new SAXException(localName + " must be the root element!"); } Rectangle targetRect = XMLUtil.getAttributeAsRectangle(attributes, "rect"); + structurePointer = attributes.getValue("ptr"); Link link = new Link(null, targetRect); objectStack.push(link); } else if (GOTO_XY.getLocalName().equals(localName)) { @@ -118,6 +121,9 @@ public class DocumentNavigationHandler extends DefaultHandler } action = new GoToXYAction(id, pageIndex, location); } + if (structurePointer != null) { + action.setStructurePointer(structurePointer); + } objectStack.push(action); } else if (GOTO_URI.getLocalName().equals(localName)) { String id = attributes.getValue("id"); @@ -128,6 +134,9 @@ public class DocumentNavigationHandler extends DefaultHandler if (id != null) { action.setID(id); } + if (structurePointer != null) { + action.setStructurePointer(structurePointer); + } objectStack.push(action); } else { throw new SAXException( 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..2c13edca5 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java @@ -0,0 +1,150 @@ +/* + * 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. + */ +final 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"); + private static final PDFName NON_STRUCT = new PDFName("NonStruct"); + + static { + addMapping("block", "P"); + + PDFName st = new PDFName("Div"); + addMapping("block-container", st); + addMapping("inline-container", st); + addMapping("table-and-caption", st); + addMapping("float", st); + + 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-caption", "Caption"); + 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); + } else { + return NON_STRUCT; + } + } + + 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; + } + } + + } + + private FOToPDFRoleMap() { } +} diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java index fe5be4a39..fb5fc4e8d 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 mcid Sequence number + */ + protected void beginMarkedContentSequence(String structElemType, int mcid) { + assert !this.inMarkedContentSequence; + assert !this.inArtifactMode; + if (structElemType != null) { + currentStream.add(structElemType + " <</MCID " + String.valueOf(mcid) + ">>\n" + + "BDC\n"); + } else { + currentStream.add("/Artifact\nBMC\n"); + this.inArtifactMode = true; + } + this.inMarkedContentSequence = true; + } + + void endMarkedContentSequence() { + currentStream.add("EMC\n"); + this.inMarkedContentSequence = false; + this.inArtifactMode = false; + } + /** * Restored the graphics state valid before the previous {@link #saveGraphicsState()}. * @param popState true if the state should also be popped, false if only the PDF command @@ -166,11 +202,42 @@ public class PDFContentGenerator { } } - /** {@inheritDoc} */ + /** + * Same as {@link #restoreGraphicsState(boolean)}, with <code>true</code> as + * a parameter. + */ protected void restoreGraphicsState() { restoreGraphicsState(true); } + /** + * Same as {@link #restoreGraphicsState()}, additionally ending the current + * marked content sequence if any. + */ + protected void restoreGraphicsStateAccess() { + endTextObject(); + currentStream.add("Q\n"); + if (this.inMarkedContentSequence) { + endMarkedContentSequence(); + } + currentState.restore(); + } + + /** + * Separates 2 text elements, ending the current marked content sequence and + * starting a new one. + * + * @param structElemType structure element type + * @param mcid sequence number + * @see #beginMarkedContentSequence(String, int) + */ + protected void separateTextElements(String structElemType, int mcid) { + textutil.endTextObject(); + endMarkedContentSequence(); + beginMarkedContentSequence(structElemType, mcid); + textutil.beginTextObject(); + } + /** Indicates the beginning of a text object. */ protected void beginTextObject() { if (!textutil.isInTextObject()) { @@ -178,9 +245,27 @@ public class PDFContentGenerator { } } + /** + * Indicates the beginning of a marked-content text object. + * + * @param structElemType structure element type + * @param mcid sequence number + * @see #beginTextObject() + * @see #beginMarkedContentSequence(String, int) + */ + protected void beginTextObject(String structElemType, int mcid) { + if (!textutil.isInTextObject()) { + beginMarkedContentSequence(structElemType, mcid); + textutil.beginTextObject(); + } + } + /** Indicates the end of a text object. */ protected void endTextObject() { if (textutil.isInTextObject()) { + if (this.inMarkedContentSequence) { + endMarkedContentSequence(); + } textutil.endTextObject(); } } @@ -326,5 +411,28 @@ public class PDFContentGenerator { restoreGraphicsState(); } + /** + * Places a previously registered image at a certain place on the page, + * bracketing it as a marked-content sequence. + * + * @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 structure element type + * @param mcid sequence number + * @see #beginMarkedContentSequence(String, int) + */ + 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 6f4a338d6..3cd601bfa 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -28,6 +28,8 @@ import java.awt.geom.Rectangle2D.Double; import java.io.IOException; import java.util.Map; +import org.w3c.dom.NodeList; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -50,6 +52,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; /** * {@link IFDocumentHandler} implementation that produces PDF. @@ -59,6 +62,12 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** logging instance */ private static Log log = LogFactory.getLog(PDFDocumentHandler.class); + private int pageSequenceIndex; + + private boolean accessEnabled; + + private PDFLogicalStructureHandler logicalStructureHandler; + /** the PDF Document being created */ protected PDFDocument pdfDoc; @@ -86,7 +95,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); /** @@ -97,7 +106,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public boolean supportsPagesOutOfOrder() { - return true; + return !accessEnabled; } /** {@inheritDoc} */ @@ -125,11 +134,20 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { return this.pdfUtil; } + PDFLogicalStructureHandler getLogicalStructureHandler() { + return logicalStructureHandler; + } + /** {@inheritDoc} */ public void startDocument() throws IFException { super.startDocument(); try { this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream); + this.accessEnabled = getUserAgent().isAccessibilityEnabled(); + if (accessEnabled) { + pdfDoc.getRoot().makeTagged(); + logicalStructureHandler = new PDFLogicalStructureHandler(pdfDoc); + } } catch (IOException e) { throw new IFException("I/O error in startDocument()", e); } @@ -145,7 +163,6 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { try { pdfDoc.getResources().addFonts(pdfDoc, fontInfo); pdfDoc.outputTrailer(this.outputStream); - this.pdfDoc = null; pdfResources = null; @@ -160,7 +177,18 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@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 (accessEnabled) { + NodeList nodes = getUserAgent().getStructureTree().getPageSequence(pageSequenceIndex++); + logicalStructureHandler.processStructureTree(nodes, getContext().getLanguage()); + } } /** {@inheritDoc} */ @@ -198,13 +226,17 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { toPointAndScale(cropBox, scaleX, scaleY), toPointAndScale(bleedBox, scaleX, scaleY), toPointAndScale(trimBox, scaleX, scaleY)); + if (accessEnabled) { + logicalStructureHandler.startPage(currentPage); + } 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); @@ -221,7 +253,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public IFPainter startPageContent() throws IFException { - return new PDFPainter(this); + return new PDFPainter(this, logicalStructureHandler); } /** {@inheritDoc} */ @@ -231,6 +263,9 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void endPage() throws IFException { + if (accessEnabled) { + logicalStructureHandler.endPage(); + } try { this.documentNavigationHandler.commit(); this.pdfDoc.registerObject(generator.getStream()); @@ -267,8 +302,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(); diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java index 3e1024d98..5e1b1b250 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java @@ -47,10 +47,10 @@ import org.apache.fop.render.pdf.PDFDocumentHandler.PageReference; */ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler { - private PDFDocumentHandler documentHandler; + private final 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 +111,11 @@ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler PDFLink pdfLink = getPDFDoc().getFactory().makeLink( targetRect2D, pdfAction); if (pdfLink != null) { + String ptr = link.getAction().getStructurePointer(); + if (documentHandler.getUserAgent().isAccessibilityEnabled() + && ptr != null && ptr.length() > 0) { + documentHandler.getLogicalStructureHandler().addLinkContentItem(pdfLink, ptr); + } documentHandler.currentPage.addAnnotation(pdfLink); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java index 18717809d..c3242827a 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java @@ -35,6 +35,7 @@ import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.AbstractImageHandlerGraphics2D; import org.apache.fop.render.RendererContext; import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo; import org.apache.fop.svg.PDFGraphics2D; /** @@ -63,6 +64,9 @@ public class PDFImageHandlerGraphics2D extends AbstractImageHandlerGraphics2D renderer.currentPage, renderer.getFontInfo()); Rectangle effPos = new Rectangle(origin.x + pos.x, origin.y + pos.y, pos.width, pos.height); + if (context.getUserAgent().isAccessibilityEnabled()) { + pdfContext.setMarkedContentInfo(renderer.addCurrentImageToStructureTree()); + } handleImage(pdfContext, image, effPos); return null; } @@ -87,7 +91,13 @@ public class PDFImageHandlerGraphics2D extends AbstractImageHandlerGraphics2D float sy = fheight / (float)imh; generator.comment("G2D start"); - generator.saveGraphicsState(); + boolean accessibilityEnabled = context.getUserAgent().isAccessibilityEnabled(); + if (accessibilityEnabled) { + MarkedContentInfo mci = pdfContext.getMarkedContentInfo(); + generator.saveGraphicsState(mci.tag, mci.mcid); + } else { + generator.saveGraphicsState(); + } generator.updateColor(Color.black, false, null); generator.updateColor(Color.black, true, null); @@ -115,7 +125,11 @@ public class PDFImageHandlerGraphics2D extends AbstractImageHandlerGraphics2D imageG2D.getGraphics2DImagePainter().paint(graphics, area); generator.add(graphics.getString()); - generator.restoreGraphicsState(); + if (accessibilityEnabled) { + generator.restoreGraphicsStateAccess(); + } else { + generator.restoreGraphicsState(); + } generator.comment("G2D end"); } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java index d47d5a439..02dd98ecf 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java @@ -34,6 +34,7 @@ import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RendererContext; import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo; /** * Image handler implementation which handles raw JPEG images for PDF output. @@ -82,7 +83,12 @@ 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()) { + MarkedContentInfo mci = pdfContext.getMarkedContentInfo(); + generator.placeImage(x, y, w, h, xobj, mci.tag, mci.mcid); + } 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..3c02cb6f3 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java @@ -34,6 +34,7 @@ import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RendererContext; import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo; /** * Image handler implementation which handles RenderedImage instances for PDF output. @@ -83,7 +84,12 @@ 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()) { + MarkedContentInfo mci = pdfContext.getMarkedContentInfo(); + generator.placeImage(x, y, w, h, xobj, mci.tag, mci.mcid); + } 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..e6d2c8a71 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java @@ -40,6 +40,7 @@ import org.apache.fop.apps.FOUserAgent; import org.apache.fop.image.loader.batik.BatikImageFlavors; import org.apache.fop.render.ImageHandler; import org.apache.fop.render.RenderingContext; +import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo; import org.apache.fop.svg.PDFAElementBridge; import org.apache.fop.svg.PDFBridgeContext; import org.apache.fop.svg.PDFGraphics2D; @@ -101,8 +102,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 +122,10 @@ public class PDFImageHandlerSVG implements ImageHandler { */ generator.comment("SVG setup"); generator.saveGraphicsState(); + if (context.getUserAgent().isAccessibilityEnabled()) { + MarkedContentInfo mci = pdfContext.getMarkedContentInfo(); + generator.beginMarkedContentSequence(mci.tag, mci.mcid); + } 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/PDFLogicalStructureHandler.java b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java new file mode 100644 index 000000000..d55094d48 --- /dev/null +++ b/src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java @@ -0,0 +1,299 @@ +/* + * 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.HashMap; +import java.util.Locale; +import java.util.Map; + +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.apache.fop.fo.extensions.ExtensionElementMapping; +import org.apache.fop.fo.extensions.InternalElementMapping; +import org.apache.fop.pdf.PDFArray; +import org.apache.fop.pdf.PDFDictionary; +import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFLink; +import org.apache.fop.pdf.PDFName; +import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFParentTree; +import org.apache.fop.pdf.PDFStructElem; +import org.apache.fop.pdf.PDFStructTreeRoot; + + +/** + * Handles the creation of the logical structure in the PDF document. + */ +class PDFLogicalStructureHandler { + + private static final PDFName MCR = new PDFName("MCR"); + + private static final PDFName OBJR = new PDFName("OBJR"); + + private static final MarkedContentInfo ARTIFACT = new MarkedContentInfo(null, -1, null); + + private final PDFDocument pdfDoc; + + /** + * Map of references to the corresponding structure elements. + */ + private final Map structTreeMap = new HashMap(); + + private final PDFParentTree parentTree = new PDFParentTree(); + + private int parentTreeKey; + + private PDFPage currentPage; + + /** + * The array of references, from marked-content sequences in the current + * page, to their parent structure elements. This will be a value in the + * structure parent tree, whose corresponding key will be the page's + * StructParents entry. + */ + private PDFArray pageParentTreeArray; + + private PDFStructElem rootStructureElement; + + /** + * Class providing the necessary information for bracketing content + * associated to a structure element as a marked-content sequence. + */ + static final class MarkedContentInfo { + + /** + * A value that can be used for the tag operand of a marked-content + * operator. This is the structure type of the corresponding structure + * element. + */ + final String tag; + + /** + * The value for the MCID entry of the marked-content sequence's property list. + */ + final int mcid; + + private final PDFStructElem parent; + + private MarkedContentInfo(String tag, int mcid, PDFStructElem parent) { + this.tag = tag; + this.mcid = mcid; + this.parent = parent; + } + } + + /** + * Creates a new instance for handling the logical structure of the given document. + * + * @param pdfDoc a document + */ + PDFLogicalStructureHandler(PDFDocument pdfDoc) { + this.pdfDoc = pdfDoc; + PDFStructTreeRoot structTreeRoot = pdfDoc.getFactory().makeStructTreeRoot(parentTree); + rootStructureElement = pdfDoc.getFactory().makeStructureElement( + FOToPDFRoleMap.mapFormattingObject("root", structTreeRoot), structTreeRoot); + structTreeRoot.addKid(rootStructureElement); + } + + /** + * Converts the given structure tree into PDF. + * + * @param structureTree the structure tree of the current page sequence + * @param language language set on the page sequence + */ + void processStructureTree(NodeList structureTree, Locale language) { + pdfDoc.enforceLanguageOnRoot(); + PDFStructElem structElemPart = pdfDoc.getFactory().makeStructureElement( + FOToPDFRoleMap.mapFormattingObject("page-sequence", rootStructureElement), + rootStructureElement); + rootStructureElement.addKid(structElemPart); + if (language != null) { + structElemPart.setLanguage(language); + } + + for (int i = 0, n = structureTree.getLength(); i < n; i++) { + Node node = structureTree.item(i); + assert node.getLocalName().equals("flow") + || node.getLocalName().equals("static-content"); + PDFStructElem structElemSect = pdfDoc.getFactory().makeStructureElement( + FOToPDFRoleMap.mapFormattingObject(node.getLocalName(), structElemPart), + structElemPart); + structElemPart.addKid(structElemSect); + NodeList childNodes = node.getChildNodes(); + for (int j = 0, m = childNodes.getLength(); j < m; j++) { + processNode(childNodes.item(j), structElemSect, true); + } + } + } + + private void processNode(Node node, PDFStructElem parent, boolean addKid) { + Node attr = node.getAttributes().getNamedItemNS(InternalElementMapping.URI, "ptr"); + assert attr != null; + String ptr = attr.getNodeValue(); + String nodeName = node.getLocalName(); + PDFStructElem structElem = pdfDoc.getFactory().makeStructureElement( + FOToPDFRoleMap.mapFormattingObject(nodeName, parent), parent); + // TODO necessary? If a page-sequence is empty (e.g., contains a single + // empty fo:block), should the block still be added to the structure + // tree? This is not being done for descendant empty elements... + if (addKid) { + parent.addKid(structElem); + } + if (nodeName.equals("external-graphic") || nodeName.equals("instream-foreign-object")) { + Node altTextNode = node.getAttributes().getNamedItemNS( + ExtensionElementMapping.URI, "alt-text"); + if (altTextNode != null) { + structElem.put("Alt", altTextNode.getNodeValue()); + } else { + structElem.put("Alt", "No alternate text specified"); + } + } + structTreeMap.put(ptr, structElem); + NodeList nodes = node.getChildNodes(); + for (int i = 0, n = nodes.getLength(); i < n; i++) { + processNode(nodes.item(i), structElem, false); + } + } + + private int getNextParentTreeKey() { + return parentTreeKey++; + } + + /** + * Receive notification of the beginning of a new page. + * + * @param page the page that will be rendered in PDF + */ + void startPage(PDFPage page) { + currentPage = page; + currentPage.setStructParents(getNextParentTreeKey()); + pageParentTreeArray = new PDFArray(); + } + + /** + * Receive notification of the end of the current page. + */ + void endPage() { + // TODO + // Values in a number tree must be indirect references to the PDF + // objects associated to the keys. To enforce that the array is + // registered to the PDF document. Unfortunately that can't be done + // earlier since a call to PDFContentGenerator.flushPDFDoc can be made + // before the array is complete, which would result in only part of it + // being output to the PDF. + // This should really be handled by PDFNumsArray + pdfDoc.registerObject(pageParentTreeArray); + parentTree.getNums().put(currentPage.getStructParents(), pageParentTreeArray); + } + + private MarkedContentInfo addToParentTree(String structurePointer) { + PDFStructElem parent = (PDFStructElem) structTreeMap.get(structurePointer); + if (parent == null) { + return ARTIFACT; + } else { + pageParentTreeArray.add(parent); + String type = parent.getStructureType().toString(); + int mcid = pageParentTreeArray.length() - 1; + return new MarkedContentInfo(type, mcid, parent); + } + } + + /** + * Adds a content item corresponding to text into the structure tree, if + * there is a structure element associated to it. + * + * @param structurePointer reference to the parent structure element of the + * piece of text + * @return the necessary information for bracketing the content as a + * marked-content sequence. If there is no element in the structure tree + * associated to that content, returns an instance whose + * {@link MarkedContentInfo#tag} value is <code>null</code>. The content + * must then be treated as an artifact. + */ + MarkedContentInfo addTextContentItem(String structurePointer) { + MarkedContentInfo mci = addToParentTree(structurePointer); + if (mci != ARTIFACT) { + PDFDictionary contentItem = new PDFDictionary(); + contentItem.put("Type", MCR); + contentItem.put("Pg", this.currentPage); + contentItem.put("MCID", mci.mcid); + mci.parent.addKid(contentItem); + } + return mci; + } + + /** + * Adds a content item corresponding to an image into the structure tree, if + * there is a structure element associated to it. + * + * @param structurePointer reference to the parent structure element of the + * image + * @return the necessary information for bracketing the content as a + * marked-content sequence. If there is no element in the structure tree + * associated to that image, returns an instance whose + * {@link MarkedContentInfo#tag} value is <code>null</code>. The image + * must then be treated as an artifact. + */ + MarkedContentInfo addImageContentItem(String structurePointer) { + MarkedContentInfo mci = addToParentTree(structurePointer); + if (mci != ARTIFACT) { + mci.parent.setMCIDKid(mci.mcid); + mci.parent.setPage(this.currentPage); + } + return mci; + } + + // While the PDF spec allows images to be referred as PDF objects, this + // makes the Acrobat Pro checker complain that the image is not accessible. + // Its alt-text is still read aloud though. Using marked-content sequences + // like for text works. +// MarkedContentInfo addImageObject(String parentReference) { +// MarkedContentInfo mci = addToParentTree(parentReference); +// if (mci != ARTIFACT) { +// PDFDictionary contentItem = new PDFDictionary(); +// contentItem.put("Type", OBJR); +// contentItem.put("Pg", this.currentPage); +// contentItem.put("Obj", null); +// mci.parent.addKid(contentItem); +// } +// return mci; +// } + + /** + * Adds a content item corresponding to the given link into the structure + * tree. + * + * @param link a link + * @param structurePointer reference to the corresponding parent structure element + */ + void addLinkContentItem(PDFLink link, String structurePointer) { + int structParent = getNextParentTreeKey(); + link.setStructParent(structParent); + parentTree.getNums().put(structParent, link); + PDFDictionary contentItem = new PDFDictionary(); + contentItem.put("Type", OBJR); + contentItem.put("Pg", this.currentPage); + contentItem.put("Obj", link); + PDFStructElem parent = (PDFStructElem) structTreeMap.get(structurePointer); + parent.addKid(contentItem); + } + +} diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index fa00fd7b4..f72f09ad0 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -29,9 +29,6 @@ import java.io.IOException; import org.w3c.dom.Document; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import org.apache.fop.fonts.Font; import org.apache.fop.fonts.FontInfo; import org.apache.fop.fonts.FontTriplet; @@ -47,6 +44,7 @@ import org.apache.fop.render.intermediate.AbstractIFPainter; import org.apache.fop.render.intermediate.IFContext; import org.apache.fop.render.intermediate.IFException; import org.apache.fop.render.intermediate.IFState; +import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo; import org.apache.fop.traits.BorderProps; import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.CharUtilities; @@ -56,26 +54,33 @@ import org.apache.fop.util.CharUtilities; */ 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; + + private MarkedContentInfo imageMCI; + + private PDFLogicalStructureHandler logicalStructureHandler; /** * Default constructor. * @param documentHandler the parent document handler + * @param logicalStructureHandler the logical structure handler */ - public PDFPainter(PDFDocumentHandler documentHandler) { + public PDFPainter(PDFDocumentHandler documentHandler, + PDFLogicalStructureHandler logicalStructureHandler) { super(); this.documentHandler = documentHandler; + this.logicalStructureHandler = logicalStructureHandler; this.generator = documentHandler.generator; this.borderPainter = new PDFBorderPainter(this.generator); this.state = IFState.create(); + accessEnabled = this.getUserAgent().isAccessibilityEnabled(); } /** {@inheritDoc} */ @@ -122,22 +127,36 @@ 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); - return; + if (accessEnabled) { + String ptr = getContext().getStructurePointer(); + prepareImageMCID(ptr); + placeImageAccess(rect, xobject); + } else { + placeImage(rect, xobject); + } + } else { + if (accessEnabled) { + String ptr = getContext().getStructurePointer(); + prepareImageMCID(ptr); + } + drawImageUsingURI(uri, rect); + flushPDFDoc(); } + } - drawImageUsingURI(uri, rect); - - flushPDFDoc(); + private void prepareImageMCID(String ptr) { + imageMCI = logicalStructureHandler.addImageContentItem(ptr); } /** {@inheritDoc} */ protected RenderingContext createRenderingContext() { PDFRenderingContext pdfContext = new PDFRenderingContext( getUserAgent(), generator, this.documentHandler.currentPage, getFontInfo()); + pdfContext.setMarkedContentInfo(imageMCI); return pdfContext; } @@ -158,11 +177,31 @@ 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(imageMCI.tag, imageMCI.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 { + if (accessEnabled) { + String ptr = getContext().getStructurePointer(); + prepareImageMCID(ptr); + } drawImageUsingDocument(doc, rect); - flushPDFDoc(); } @@ -253,10 +292,22 @@ 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(); + MarkedContentInfo mci = logicalStructureHandler.addTextContentItem(ptr); + if (generator.getTextUtil().isInTextObject()) { + generator.separateTextElements(mci.tag, mci.mcid); + } + generator.updateColor(state.getTextColor(), true, null); + generator.beginTextObject(mci.tag, mci.mcid); + } 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 +328,7 @@ public class PDFPainter extends AbstractIFPainter { PDFTextUtil textutil = generator.getTextUtil(); textutil.updateTf(fontKey, fontSize, tf.isMultiByte()); - generator.updateCharacterSpacing((float)letterSpacing / 1000f); + generator.updateCharacterSpacing(letterSpacing / 1000f); textutil.writeTextMatrix(new AffineTransform(1, 0, 0, -1, x / 1000f, y / 1000f)); int l = text.length(); diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderer.java b/src/java/org/apache/fop/render/pdf/PDFRenderer.java index 3b737150b..9fe08c2e4 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -31,8 +31,12 @@ import java.io.IOException; import java.io.OutputStream; import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; +import org.w3c.dom.Document; +import org.w3c.dom.NodeList; + import org.apache.xmlgraphics.image.loader.ImageException; import org.apache.xmlgraphics.image.loader.ImageFlavor; import org.apache.xmlgraphics.image.loader.ImageInfo; @@ -61,6 +65,7 @@ import org.apache.fop.area.inline.InlineParent; import org.apache.fop.area.inline.Leader; import org.apache.fop.area.inline.SpaceArea; import org.apache.fop.area.inline.TextArea; +import org.apache.fop.area.inline.Viewport; import org.apache.fop.area.inline.WordArea; import org.apache.fop.datatypes.URISpecification; import org.apache.fop.events.ResourceEventProducer; @@ -91,9 +96,11 @@ import org.apache.fop.pdf.PDFXObject; import org.apache.fop.render.AbstractPathOrientedRenderer; import org.apache.fop.render.Graphics2DAdapter; import org.apache.fop.render.RendererContext; +import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo; import org.apache.fop.traits.RuleStyle; import org.apache.fop.util.AbstractPaintingState; import org.apache.fop.util.CharUtilities; +import org.apache.fop.util.XMLUtil; import org.apache.fop.util.AbstractPaintingState.AbstractData; /** @@ -127,7 +134,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf * this is used for prepared pages that cannot be immediately * rendered */ - protected Map pages = null; + private Map pages; /** * Maps unique PageViewport key to PDF page reference @@ -193,6 +200,14 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf /** Image handler registry */ private final PDFImageHandlerRegistry imageHandlerRegistry = new PDFImageHandlerRegistry(); + private boolean accessEnabled; + + private PDFLogicalStructureHandler logicalStructureHandler; + + private int pageSequenceIndex; + + /** Reference in the structure tree to the image being rendered. */ + private String imageReference; /** * create the PDF renderer @@ -204,6 +219,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf public void setUserAgent(FOUserAgent agent) { super.setUserAgent(agent); this.pdfUtil = new PDFRenderingUtil(getUserAgent()); + accessEnabled = agent.isAccessibilityEnabled(); } PDFRenderingUtil getPDFUtil() { @@ -225,6 +241,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf } ostream = stream; this.pdfDoc = pdfUtil.setupPDFDocument(stream); + if (accessEnabled) { + pdfDoc.getRoot().makeTagged(); + logicalStructureHandler = new PDFLogicalStructureHandler(pdfDoc); + } } /** @@ -274,8 +294,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf * {@inheritDoc} */ public boolean supportsOutOfOrder() { - //return false; - return true; + return !accessEnabled; } /** @@ -394,17 +413,24 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf info.setTitle(str); } } + Locale language = null; if (pageSequence.getLanguage() != null) { String lang = pageSequence.getLanguage(); String country = pageSequence.getCountry(); - String langCode = lang + (country != null ? "-" + country : ""); + if (lang != null) { + language = (country == null) ? new Locale(lang) : new Locale(lang, country); + } if (pdfDoc.getRoot().getLanguage() == null) { //Only set if not set already (first non-null is used) //Note: No checking is performed whether the values are valid! - pdfDoc.getRoot().setLanguage(langCode); + pdfDoc.getRoot().setLanguage(XMLUtil.toRFC3066(language)); } } pdfUtil.generateDefaultXMPMetadata(); + if (accessEnabled) { + NodeList nodes = getUserAgent().getStructureTree().getPageSequence(pageSequenceIndex++); + logicalStructureHandler.processStructureTree(nodes, language); + } } /** @@ -457,6 +483,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf } currentPageRef = currentPage.referencePDF(); + if (accessEnabled) { + logicalStructureHandler.startPage(currentPage); + } + Rectangle bounds = page.getViewArea(); pageHeight = bounds.height; @@ -474,6 +504,10 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf super.renderPage(page); + if (accessEnabled) { + logicalStructureHandler.endPage(); + } + this.pdfDoc.registerObject(generator.getStream()); currentPage.setContents(generator.getStream()); PDFAnnotList annots = currentPage.getAnnotations(); @@ -903,11 +937,22 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf + pdfDoc.getProfile()); } else if (action != null) { PDFLink pdfLink = factory.makeLink(ipRect, action); + if (accessEnabled) { + String ptr = (String) ip.getTrait(Trait.PTR); + logicalStructureHandler.addLinkContentItem(pdfLink, ptr); + } currentPage.addAnnotation(pdfLink); } } } + /** {@inheritDoc} */ + public void renderViewport(Viewport viewport) { + imageReference = (String) viewport.getTrait(Trait.PTR); + super.renderViewport(viewport); + imageReference = null; + } + private Typeface getTypeface(String fontName) { Typeface tf = (Typeface) fontInfo.getFonts().get(fontName); if (tf instanceof LazyFont) { @@ -922,7 +967,16 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf Color ct = (Color) text.getTrait(Trait.COLOR); updateColor(ct, true); - beginTextObject(); + if (accessEnabled) { + String ptr = (String) text.getTrait(Trait.PTR); + MarkedContentInfo mci = logicalStructureHandler.addTextContentItem(ptr); + if (generator.getTextUtil().isInTextObject()) { + generator.separateTextElements(mci.tag, mci.mcid); + } + generator.beginTextObject(mci.tag, mci.mcid); + } else { + beginTextObject(); + } String fontName = getInternalFontNameForArea(text); int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); @@ -1178,13 +1232,22 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf * @param xobj the image XObject */ public void placeImage(float x, float y, float w, float h, PDFXObject xobj) { - saveGraphicsState(); + if (accessEnabled) { + MarkedContentInfo mci = logicalStructureHandler.addImageContentItem(imageReference); + generator.saveGraphicsState(mci.tag, mci.mcid); + } else { + saveGraphicsState(); + } generator.add(format(w) + " 0 0 " + format(-h) + " " + format(currentIPPosition / 1000f + x) + " " + format(currentBPPosition / 1000f + h + y) + " cm\n" + xobj.getName() + " Do\n"); - restoreGraphicsState(); + if (accessEnabled) { + generator.restoreGraphicsStateAccess(); + } else { + restoreGraphicsState(); + } } /** {@inheritDoc} */ @@ -1205,6 +1268,18 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf return context; } + /** {@inheritDoc} */ + public void renderDocument(Document doc, String ns, Rectangle2D pos, Map foreignAttributes) { + if (accessEnabled) { + MarkedContentInfo mci = logicalStructureHandler.addImageContentItem(imageReference); + generator.beginMarkedContentSequence(mci.tag, mci.mcid); + } + super.renderDocument(doc, ns, pos, foreignAttributes); + if (accessEnabled) { + generator.endMarkedContentSequence(); + } + } + /** * Render leader area. * This renders a leader area which is an area with a rule. @@ -1272,5 +1347,9 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf public void setEncryptionParams(PDFEncryptionParams encryptionParams) { this.pdfUtil.setEncryptionParams(encryptionParams); } + + MarkedContentInfo addCurrentImageToStructureTree() { + return logicalStructureHandler.addImageContentItem(imageReference); + } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java index 98b0c8203..80adfa5c8 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java @@ -25,6 +25,7 @@ import org.apache.fop.apps.FOUserAgent; import org.apache.fop.fonts.FontInfo; import org.apache.fop.pdf.PDFPage; import org.apache.fop.render.AbstractRenderingContext; +import org.apache.fop.render.pdf.PDFLogicalStructureHandler.MarkedContentInfo; /** * Rendering context for PDF production. @@ -34,6 +35,7 @@ public class PDFRenderingContext extends AbstractRenderingContext { private PDFContentGenerator generator; private FontInfo fontInfo; private PDFPage page; + private MarkedContentInfo mci; /** * Main constructor. @@ -79,4 +81,11 @@ public class PDFRenderingContext extends AbstractRenderingContext { return this.fontInfo; } + void setMarkedContentInfo(MarkedContentInfo mci) { + this.mci = mci; + } + + MarkedContentInfo getMarkedContentInfo() { + return mci; + } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index 2e3c83126..3d68812b1 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.Accessibility; 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(Accessibility.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/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/render/xml/XMLRenderer.java b/src/java/org/apache/fop/render/xml/XMLRenderer.java index 8e8ae3f1d..a8a1a1911 100644 --- a/src/java/org/apache/fop/render/xml/XMLRenderer.java +++ b/src/java/org/apache/fop/render/xml/XMLRenderer.java @@ -34,7 +34,8 @@ import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; - +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.apache.xmlgraphics.util.QName; @@ -86,6 +87,7 @@ import org.apache.fop.render.Renderer; import org.apache.fop.render.RendererContext; import org.apache.fop.render.XMLHandler; import org.apache.fop.util.ColorUtil; +import org.apache.fop.util.DOM2SAX; /** * Renderer that renders areas to XML for debugging purposes. @@ -105,6 +107,8 @@ public class XMLRenderer extends AbstractXMLRenderer { /** If not null, the XMLRenderer will mimic another renderer by using its font setup. */ protected Renderer mimic; + private int pageSequenceIndex; + /** * Creates a new XML renderer. */ @@ -440,6 +444,20 @@ public class XMLRenderer extends AbstractXMLRenderer { } transferForeignObjects(pageSequence); startElement("pageSequence", atts); + if (this.getUserAgent().isAccessibilityEnabled()) { + String structureTreeElement = "structureTree"; + startElement(structureTreeElement); + NodeList nodes = getUserAgent().getStructureTree().getPageSequence(pageSequenceIndex++); + for (int i = 0, n = nodes.getLength(); i < n; i++) { + Node node = nodes.item(i); + try { + new DOM2SAX(handler).writeFragment(node); + } catch (SAXException e) { + handleSAXException(e); + } + } + endElement(structureTreeElement); + } handleExtensionAttachments(pageSequence.getExtensionAttachments()); LineArea seqTitle = pageSequence.getTitle(); if (seqTitle != null) { 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/TransformerDefaultHandler.java b/src/java/org/apache/fop/util/TransformerDefaultHandler.java new file mode 100644 index 000000000..cf07cc836 --- /dev/null +++ b/src/java/org/apache/fop/util/TransformerDefaultHandler.java @@ -0,0 +1,160 @@ +/* + * 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.util; + +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.ext.DefaultHandler2; +import org.xml.sax.helpers.AttributesImpl; + +/** + * A DefaultHandler implementation that delegates all the method calls to a + * {@link TransformerHandler} instance. + */ +public class TransformerDefaultHandler extends DefaultHandler2 { + + private TransformerHandler transformerHandler; + + /** + * Creates a new instance delegating to the given TransformerHandler object. + * + * @param transformerHandler the object to which all the method calls will + * be delegated + */ + public TransformerDefaultHandler(TransformerHandler transformerHandler) { + this.transformerHandler = transformerHandler; + } + + /** + * Returns the delegate TransformerHandler instance. + * + * @return the object to which all method calls are delegated + */ + public TransformerHandler getTransformerHandler() { + return transformerHandler; + } + + /** {@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 startPrefixMapping(String prefix, String uri) throws SAXException { + transformerHandler.startPrefixMapping(prefix, uri); + } + + /** {@inheritDoc} */ + public void endPrefixMapping(String string) throws SAXException { + transformerHandler.endPrefixMapping(string); + } + + /** {@inheritDoc} */ + public void startElement(String uri, String localName, String qName, Attributes attrs) + throws SAXException { + AttributesImpl ai = new AttributesImpl(attrs); + transformerHandler.startElement(uri, localName, qName, ai); + } + + /** {@inheritDoc} */ + public void endElement(String uri, String localName, String qName) throws SAXException { + transformerHandler.endElement(uri, localName, qName); + } + + /** {@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 processingInstruction(String target, String data) throws SAXException { + transformerHandler.processingInstruction(target, data); + } + + /** {@inheritDoc} */ + public void skippedEntity(String name) throws SAXException { + transformerHandler.skippedEntity(name); + } + + /** {@inheritDoc} */ + public void notationDecl(String name, String publicId, String systemId) throws SAXException { + transformerHandler.notationDecl(name, publicId, systemId); + } + + /** {@inheritDoc} */ + public void unparsedEntityDecl(String name, String publicId, String systemId, + String notationName) throws SAXException { + transformerHandler.unparsedEntityDecl(name, publicId, systemId, notationName); + } + + /** {@inheritDoc} */ + public void startDTD(String name, String pid, String lid) throws SAXException { + transformerHandler.startDTD(name, pid, lid); + } + + /** {@inheritDoc} */ + public void endDTD() throws SAXException { + transformerHandler.endDTD(); + } + + /** {@inheritDoc} */ + public void startEntity(String name) throws SAXException { + transformerHandler.startEntity(name); + } + + /** {@inheritDoc} */ + public void endEntity(String name) throws SAXException { + transformerHandler.endEntity(name); + } + + /** {@inheritDoc} */ + public void startCDATA() throws SAXException { + transformerHandler.startCDATA(); + } + + /** {@inheritDoc} */ + public void endCDATA() throws SAXException { + transformerHandler.endCDATA(); + } + + /** {@inheritDoc} */ + public void comment(char[] charArray, int start, int length) throws SAXException { + transformerHandler.comment(charArray, start, length); + } + +} diff --git a/src/java/org/apache/fop/util/XMLUtil.java b/src/java/org/apache/fop/util/XMLUtil.java index d8fe12c27..f7c013a2f 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]); + } + } + } |