summaryrefslogtreecommitdiffstats
path: root/src/java
diff options
context:
space:
mode:
authorVincent Hennebert <vhennebert@apache.org>2009-10-27 19:07:52 +0000
committerVincent Hennebert <vhennebert@apache.org>2009-10-27 19:07:52 +0000
commitbe1d3250bd8dd170d4fe4fe8a4850ca7759cd079 (patch)
tree3ce075e8b992e368e4d06935d3b317faaa622579 /src/java
parentd7b69927f611f4da9cc9b26396e1eb0a1bdae39e (diff)
parentcea774bc9129a1e2b6f47cff523cf0e1c13fa832 (diff)
downloadxmlgraphics-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')
-rw-r--r--src/java/META-INF/services/org.apache.fop.fo.ElementMapping3
-rw-r--r--src/java/org/apache/fop/accessibility/Accessibility.java87
-rw-r--r--src/java/org/apache/fop/accessibility/AccessibilityEventProducer.java54
-rw-r--r--src/java/org/apache/fop/accessibility/AccessibilityEventProducer.xml4
-rw-r--r--src/java/org/apache/fop/accessibility/AccessibilityPreprocessor.java95
-rw-r--r--src/java/org/apache/fop/accessibility/StructureTree.java102
-rw-r--r--src/java/org/apache/fop/accessibility/StructureTreeBuilder.java95
-rw-r--r--src/java/org/apache/fop/accessibility/addPtr.xsl88
-rw-r--r--src/java/org/apache/fop/accessibility/reduceFOTree.xsl100
-rw-r--r--src/java/org/apache/fop/apps/FOUserAgent.java50
-rw-r--r--src/java/org/apache/fop/apps/Fop.java7
-rw-r--r--src/java/org/apache/fop/apps/FopFactory.java18
-rw-r--r--src/java/org/apache/fop/apps/FopFactoryConfigurator.java9
-rw-r--r--src/java/org/apache/fop/area/AreaTreeParser.java104
-rw-r--r--src/java/org/apache/fop/area/Trait.java6
-rw-r--r--src/java/org/apache/fop/cli/CommandLineOptions.java4
-rw-r--r--src/java/org/apache/fop/events/EventFormatter.xml1
-rw-r--r--src/java/org/apache/fop/fo/Constants.java10
-rw-r--r--src/java/org/apache/fop/fo/FONode.java3
-rw-r--r--src/java/org/apache/fop/fo/FOPropertyMapping.java12
-rw-r--r--src/java/org/apache/fop/fo/FOValidationEventProducer.java13
-rw-r--r--src/java/org/apache/fop/fo/extensions/ExtensionElementMapping.java2
-rw-r--r--src/java/org/apache/fop/fo/extensions/ExternalDocument.java2
-rw-r--r--src/java/org/apache/fop/fo/extensions/InternalElementMapping.java73
-rw-r--r--src/java/org/apache/fop/fo/flow/AbstractGraphics.java17
-rw-r--r--src/java/org/apache/fop/fo/flow/AbstractPageNumberCitation.java13
-rw-r--r--src/java/org/apache/fop/fo/flow/Block.java13
-rw-r--r--src/java/org/apache/fop/fo/flow/Character.java10
-rw-r--r--src/java/org/apache/fop/fo/flow/Inline.java10
-rw-r--r--src/java/org/apache/fop/fo/flow/PageNumber.java10
-rw-r--r--src/java/org/apache/fop/fo/flow/table/TableFObj.java15
-rw-r--r--src/java/org/apache/fop/fo/properties/StructurePointerPropertySet.java34
-rw-r--r--src/java/org/apache/fop/layoutmgr/BlockLayoutManager.java1
-rw-r--r--src/java/org/apache/fop/layoutmgr/TraitSetter.java11
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/AbstractGraphicsLayoutManager.java1
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/AbstractPageNumberCitationLayoutManager.java1
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/BasicLinkLayoutManager.java10
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/CharacterLayoutManager.java1
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/PageNumberLayoutManager.java2
-rw-r--r--src/java/org/apache/fop/layoutmgr/inline/TextLayoutManager.java19
-rw-r--r--src/java/org/apache/fop/pdf/PDFAMode.java13
-rw-r--r--src/java/org/apache/fop/pdf/PDFArray.java9
-rw-r--r--src/java/org/apache/fop/pdf/PDFDocument.java19
-rw-r--r--src/java/org/apache/fop/pdf/PDFFactory.java33
-rw-r--r--src/java/org/apache/fop/pdf/PDFLink.java13
-rw-r--r--src/java/org/apache/fop/pdf/PDFNumsArray.java22
-rw-r--r--src/java/org/apache/fop/pdf/PDFPage.java29
-rw-r--r--src/java/org/apache/fop/pdf/PDFParentTree.java44
-rw-r--r--src/java/org/apache/fop/pdf/PDFProfile.java29
-rw-r--r--src/java/org/apache/fop/pdf/PDFRoot.java46
-rw-r--r--src/java/org/apache/fop/pdf/PDFStructElem.java159
-rw-r--r--src/java/org/apache/fop/pdf/PDFStructTreeRoot.java55
-rw-r--r--src/java/org/apache/fop/render/AbstractRendererConfigurator.java5
-rw-r--r--src/java/org/apache/fop/render/afp/AFPEventProducer.xml1
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFConstants.java2
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFContext.java47
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFParser.java128
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFRenderer.java36
-rw-r--r--src/java/org/apache/fop/render/intermediate/IFSerializer.java39
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java17
-rw-r--r--src/java/org/apache/fop/render/intermediate/extensions/DocumentNavigationHandler.java9
-rw-r--r--src/java/org/apache/fop/render/pdf/FOToPDFRoleMap.java150
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFContentGenerator.java110
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java51
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java11
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerGraphics2D.java18
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java8
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java8
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java15
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFLogicalStructureHandler.java299
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFPainter.java91
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderer.java95
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderingContext.java9
-rw-r--r--src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java7
-rw-r--r--src/java/org/apache/fop/render/txt/TXTRenderer.java3
-rw-r--r--src/java/org/apache/fop/render/xml/XMLRenderer.java20
-rw-r--r--src/java/org/apache/fop/util/DOM2SAX.java10
-rw-r--r--src/java/org/apache/fop/util/TransformerDefaultHandler.java160
-rw-r--r--src/java/org/apache/fop/util/XMLUtil.java36
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 &lt;structure-tree&gt; or
+ * &lt;structureTree&gt; 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]);
+ }
+ }
+
}