diff options
author | Vincent Hennebert <vhennebert@apache.org> | 2009-10-27 19:07:52 +0000 |
---|---|---|
committer | Vincent Hennebert <vhennebert@apache.org> | 2009-10-27 19:07:52 +0000 |
commit | be1d3250bd8dd170d4fe4fe8a4850ca7759cd079 (patch) | |
tree | 3ce075e8b992e368e4d06935d3b317faaa622579 /src/java/org/apache/fop/render/pdf | |
parent | d7b69927f611f4da9cc9b26396e1eb0a1bdae39e (diff) | |
parent | cea774bc9129a1e2b6f47cff523cf0e1c13fa832 (diff) | |
download | xmlgraphics-fop-be1d3250bd8dd170d4fe4fe8a4850ca7759cd079.tar.gz xmlgraphics-fop-be1d3250bd8dd170d4fe4fe8a4850ca7759cd079.zip |
Merged back Temp_Accessibility branch into Trunk
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@830293 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache/fop/render/pdf')
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); |