aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache/fop/render/pdf
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/org/apache/fop/render/pdf
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/org/apache/fop/render/pdf')
-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
13 files changed, 824 insertions, 48 deletions
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);