diff options
Diffstat (limited to 'src/java/org/apache/fop/render')
27 files changed, 880 insertions, 88 deletions
diff --git a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java index 54f6170db..8da4b3973 100644 --- a/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java +++ b/src/java/org/apache/fop/render/AbstractPathOrientedRenderer.java @@ -834,8 +834,10 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param url the URI/URL of the image * @param pos the position of the image * @param foreignAttributes an optional Map with foreign attributes, may be null + * @param ptr used for accessibility */ - protected abstract void drawImage(String url, Rectangle2D pos, Map foreignAttributes); + protected abstract void drawImage(String url, Rectangle2D pos, Map foreignAttributes, + String ptr); /** * Draw an image at the indicated location. @@ -843,7 +845,7 @@ public abstract class AbstractPathOrientedRenderer extends PrintRenderer { * @param pos the position of the image */ protected final void drawImage(String url, Rectangle2D pos) { - drawImage(url, pos, null); + drawImage(url, pos, null, ""); } /** diff --git a/src/java/org/apache/fop/render/AbstractRenderer.java b/src/java/org/apache/fop/render/AbstractRenderer.java index 943c8c9da..d7df7596d 100644 --- a/src/java/org/apache/fop/render/AbstractRenderer.java +++ b/src/java/org/apache/fop/render/AbstractRenderer.java @@ -737,11 +737,13 @@ public abstract class AbstractRenderer currentBPPosition += viewport.getOffset(); Rectangle2D contpos = viewport.getContentPosition(); if (content instanceof Image) { - renderImage((Image) content, contpos); + String ptr = (String) viewport.getTrait(Trait.PTR); + renderImage((Image) content, contpos, ptr); } else if (content instanceof Container) { renderContainer((Container) content); } else if (content instanceof ForeignObject) { - renderForeignObject((ForeignObject) content, contpos); + String ptr = (String) viewport.getTrait(Trait.PTR); + renderForeignObject((ForeignObject) content, contpos, ptr); } else if (content instanceof InlineBlockParent) { renderInlineBlockParent((InlineBlockParent) content); } @@ -754,9 +756,10 @@ public abstract class AbstractRenderer * * @param image The image * @param pos The target position of the image + * @param ptr used for accessibility * (todo) Make renderImage() protected */ - public void renderImage(Image image, Rectangle2D pos) { + public void renderImage(Image image, Rectangle2D pos, String ptr) { // Default: do nothing. // Some renderers (ex. Text) don't support images. } @@ -780,9 +783,10 @@ public abstract class AbstractRenderer * * @param fo The foreign object area * @param pos The target position of the foreign object + * @param ptr used for accessibility * (todo) Make renderForeignObject() protected */ - protected void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + protected void renderForeignObject(ForeignObject fo, Rectangle2D pos, String ptr) { // Default: do nothing. // Some renderers (ex. Text) don't support foreign objects. } diff --git a/src/java/org/apache/fop/render/afp/AFPPainter.java b/src/java/org/apache/fop/render/afp/AFPPainter.java index 0c2501b87..1816ff515 100644 --- a/src/java/org/apache/fop/render/afp/AFPPainter.java +++ b/src/java/org/apache/fop/render/afp/AFPPainter.java @@ -183,7 +183,7 @@ public class AFPPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { String name = documentHandler.getPageSegmentNameFor(uri); if (name != null) { float[] srcPts = {rect.x, rect.y}; @@ -195,7 +195,7 @@ public class AFPPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { drawImageUsingDocument(doc, rect); } @@ -312,7 +312,7 @@ public class AFPPainter extends AbstractIFPainter { /** {@inheritDoc} */ public void drawText(int x, int y, final int letterSpacing, final int wordSpacing, final int[] dx, - final String text) throws IFException { + final String text, final String ptr) throws IFException { final int fontSize = this.state.getFontSize(); getPaintingState().setFontSize(fontSize); diff --git a/src/java/org/apache/fop/render/afp/AFPRenderer.java b/src/java/org/apache/fop/render/afp/AFPRenderer.java index d1d2bec3b..1967ae607 100644 --- a/src/java/org/apache/fop/render/afp/AFPRenderer.java +++ b/src/java/org/apache/fop/render/afp/AFPRenderer.java @@ -375,7 +375,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust ImageFlavor.GRAPHICS2D, ImageFlavor.BUFFERED_IMAGE, ImageFlavor.RENDERED_IMAGE }; /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + public void drawImage(String uri, Rectangle2D pos, Map foreignAttributes, String ptr) { uri = URISpecification.getURL(uri); paintingState.setImageUri(uri); @@ -506,7 +506,7 @@ public class AFPRenderer extends AbstractPathOrientedRenderer implements AFPCust /** {@inheritDoc} */ public void renderImage(Image image, Rectangle2D pos) { - drawImage(image.getURL(), pos, image.getForeignAttributes()); + drawImage(image.getURL(), pos, image.getForeignAttributes(),""); } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/intermediate/IFConstants.java b/src/java/org/apache/fop/render/intermediate/IFConstants.java index e7f7e1a00..4c9b9fc8d 100644 --- a/src/java/org/apache/fop/render/intermediate/IFConstants.java +++ b/src/java/org/apache/fop/render/intermediate/IFConstants.java @@ -50,4 +50,6 @@ public interface IFConstants extends XMLConstants { String EL_BORDER_RECT = "border-rect"; String EL_FONT = "font"; String EL_TEXT = "text"; + /** used for accessibility */ + String EL_STRUCTURE_TREE = "structure-tree"; } diff --git a/src/java/org/apache/fop/render/intermediate/IFPainter.java b/src/java/org/apache/fop/render/intermediate/IFPainter.java index 6369b0251..163fe16a1 100644 --- a/src/java/org/apache/fop/render/intermediate/IFPainter.java +++ b/src/java/org/apache/fop/render/intermediate/IFPainter.java @@ -153,10 +153,11 @@ public interface IFPainter { * @param wordSpacing additional spacing between words (may be 0) * @param dx an array of adjustment values for each character in X-direction (may be null) * @param text the text + * @param ptr used for accessibility * @throws IFException if an error occurs while handling this event */ void drawText(int x, int y, int letterSpacing, int wordSpacing, - int[] dx, String text) throws IFException; + int[] dx, String text, String ptr) throws IFException; /** * Restricts the current clipping region with the given rectangle. @@ -205,18 +206,20 @@ public interface IFPainter { * an fo:external-graphic in XSL-FO. * @param uri the image's URI * @param rect the rectangle in which the image shall be painted + * @param ptr used for accessibility * @throws IFException if an error occurs while handling this event */ - void drawImage(String uri, Rectangle rect) throws IFException; + void drawImage(String uri, Rectangle rect, String ptr) throws IFException; /** * Draws an image (represented by a DOM document) inside a given rectangle. This is the * equivalent to an fo:instream-foreign-object in XSL-FO. * @param doc the DOM document containing the foreign object * @param rect the rectangle in which the image shall be painted + * @param ptr used for accessibility * @throws IFException if an error occurs while handling this event */ - void drawImage(Document doc, Rectangle rect) throws IFException; + void drawImage(Document doc, Rectangle rect, String ptr) throws IFException; //Note: For now, all foreign objects are handled as DOM documents. At the moment, all known //implementations use a DOM anyway, so optimizing this to work with SAX wouldn't result in //any performance benefits. The IFRenderer itself has a DOM anyway. Only the IFParser could diff --git a/src/java/org/apache/fop/render/intermediate/IFParser.java b/src/java/org/apache/fop/render/intermediate/IFParser.java index 92e71d105..47e24445c 100644 --- a/src/java/org/apache/fop/render/intermediate/IFParser.java +++ b/src/java/org/apache/fop/render/intermediate/IFParser.java @@ -481,7 +481,8 @@ public class IFParser implements IFConstants { s = lastAttributes.getValue("word-spacing"); int wordSpacing = (s != null ? Integer.parseInt(s) : 0); int[] dx = XMLUtil.getAttributeAsIntArray(lastAttributes, "dx"); - painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString()); + String ptr = lastAttributes.getValue("ptr"); // used for accessibility + painter.drawText(x, y, letterSpacing, wordSpacing, dx, content.toString(), ptr); } public boolean ignoreCharacters() { @@ -576,9 +577,10 @@ public class IFParser implements IFConstants { int height = Integer.parseInt(lastAttributes.getValue("height")); Map foreignAttributes = getForeignAttributes(lastAttributes); establishForeignAttributes(foreignAttributes); + String ptr = lastAttributes.getValue("ptr"); // used for accessibility if (foreignObject != null) { painter.drawImage(foreignObject, - new Rectangle(x, y, width, height)); + new Rectangle(x, y, width, height), ptr); foreignObject = null; } else { String uri = lastAttributes.getValue( @@ -586,7 +588,7 @@ public class IFParser implements IFConstants { if (uri == null) { throw new IFException("xlink:href is missing on image", null); } - painter.drawImage(uri, new Rectangle(x, y, width, height)); + painter.drawImage(uri, new Rectangle(x, y, width, height), ptr); } resetForeignAttributes(); inForeignObject = false; diff --git a/src/java/org/apache/fop/render/intermediate/IFRenderer.java b/src/java/org/apache/fop/render/intermediate/IFRenderer.java index 558ddfab8..664f1324a 100644 --- a/src/java/org/apache/fop/render/intermediate/IFRenderer.java +++ b/src/java/org/apache/fop/render/intermediate/IFRenderer.java @@ -818,7 +818,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { currentIPPosition = saveIP; currentBPPosition = saveBP; - currentBPPosition += (int)(bv.getAllocBPD()); + currentBPPosition += (bv.getAllocBPD()); } viewportDimensionStack.pop(); } @@ -886,6 +886,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { // stuff we only need if a link must be created: Rectangle ipRect = null; AbstractAction action = null; + String ptr = (String) ip.getTrait(Trait.PTR); // used for accessibility // make sure the rect is determined *before* calling super! int ipp = currentIPPosition; int bpp = currentBPPosition + ip.getOffset(); @@ -929,6 +930,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { // warn if link trait found but not allowed, else create link if (linkTraitFound) { + action.setPtr(ptr); // used for accessibility Link link = new Link(action, ipRect); this.deferredLinks.add(link); } @@ -963,6 +965,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { String fontName = getInternalFontNameForArea(text); int size = ((Integer) text.getTrait(Trait.FONT_SIZE)).intValue(); + String ptr = (String)text.getTrait(Trait.PTR); // used for accessibility // This assumes that *all* CIDFonts use a /ToUnicode mapping Typeface tf = getTypeface(fontName); @@ -981,7 +984,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { textUtil.setStartPosition(rx, bl); textUtil.setSpacing(text.getTextLetterSpaceAdjust(), text.getTextWordSpaceAdjust()); super.renderText(text); - + textUtil.setPtr(ptr); // used for accessibility textUtil.flush(); renderTextDecoration(tf, size, text, bl, rx); } @@ -1054,15 +1057,24 @@ public class IFRenderer extends AbstractPathOrientedRenderer { private static final int INITIAL_BUFFER_SIZE = 16; private int[] dx = new int[INITIAL_BUFFER_SIZE]; private int lastDXPos = 0; - private StringBuffer text = new StringBuffer(); + private final StringBuffer text = new StringBuffer(); private int startx, starty; private int tls, tws; - private boolean combined = false; + private final boolean combined = false; + private String ptr = null; // used for accessibility void addChar(char ch) { text.append(ch); } + /** + * used for accessibility + * @param inPtr to be stored + */ + public void setPtr(String inPtr) { + ptr = inPtr; + } + void adjust(int adjust) { if (adjust != 0) { int idx = text.length(); @@ -1105,9 +1117,9 @@ public class IFRenderer extends AbstractPathOrientedRenderer { System.arraycopy(dx, 0, effDX, 0, size); } if (combined) { - painter.drawText(startx, starty, 0, 0, effDX, text.toString()); + painter.drawText(startx, starty, 0, 0, effDX, text.toString(), ptr); } else { - painter.drawText(startx, starty, tls, tws, effDX, text.toString()); + painter.drawText(startx, starty, tls, tws, effDX, text.toString(), ptr); } } catch (IFException e) { handleIFException(e); @@ -1118,12 +1130,12 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ - public void renderImage(Image image, Rectangle2D pos) { - drawImage(image.getURL(), pos, image.getForeignAttributes()); + public void renderImage(Image image, Rectangle2D pos, String ptr) { + drawImage(image.getURL(), pos, image.getForeignAttributes(), ptr); } /** {@inheritDoc} */ - protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes, String ptr) { Rectangle posInt = new Rectangle( currentIPPosition + (int)pos.getX(), currentBPPosition + (int)pos.getY(), @@ -1132,7 +1144,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { uri = URISpecification.getURL(uri); try { establishForeignAttributes(foreignAttributes); - painter.drawImage(uri, posInt); + painter.drawImage(uri, posInt, ptr); resetForeignAttributes(); } catch (IFException ife) { handleIFException(ife); @@ -1140,7 +1152,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ - public void renderForeignObject(ForeignObject fo, Rectangle2D pos) { + public void renderForeignObject(ForeignObject fo, Rectangle2D pos, String ptr) { endTextObject(); Rectangle posInt = new Rectangle( currentIPPosition + (int)pos.getX(), @@ -1150,7 +1162,7 @@ public class IFRenderer extends AbstractPathOrientedRenderer { Document doc = fo.getDocument(); try { establishForeignAttributes(fo.getForeignAttributes()); - painter.drawImage(doc, posInt); + painter.drawImage(doc, posInt, ptr); resetForeignAttributes(); } catch (IFException ife) { handleIFException(ife); diff --git a/src/java/org/apache/fop/render/intermediate/IFSerializer.java b/src/java/org/apache/fop/render/intermediate/IFSerializer.java index 5076a089f..d7d4a7539 100644 --- a/src/java/org/apache/fop/render/intermediate/IFSerializer.java +++ b/src/java/org/apache/fop/render/intermediate/IFSerializer.java @@ -25,11 +25,24 @@ import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.geom.AffineTransform; +import java.io.ByteArrayInputStream; +import java.io.IOException; import java.util.Iterator; import java.util.List; import java.util.Map; +import javax.xml.namespace.NamespaceContext; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; @@ -60,10 +73,46 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler implements IFConstants, IFPainter, IFDocumentNavigationHandler { private IFDocumentHandler mimicHandler; + private int pageSequenceCounter; // used for accessibility + private DocumentBuilder parser = null; // used for accessibility + private Document doc = null; // used for accessibility /** Holds the intermediate format state */ private IFState state; + private static class NamespaceContextImpl implements NamespaceContext { + + public String uri; + public String prefix; + + public NamespaceContextImpl() { + } + + public NamespaceContextImpl(String prefix, String uri) { + this.uri = uri; + this.prefix = prefix; + } + + public String getNamespaceURI(String prefix) { + return uri; + } + public void setNamespaceURI(String uri) { + this.uri = uri; + } + + public String getPrefix(String uri) { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + public Iterator getPrefixes(String uri) { + return null; + } + + } + /** * Default constructor. */ @@ -151,8 +200,16 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler handler.startPrefixMapping(DocumentNavigationExtensionConstants.PREFIX, DocumentNavigationExtensionConstants.NAMESPACE); handler.startElement(EL_DOCUMENT); + if (this.getUserAgent().accessibilityEnabled()) { + pageSequenceCounter = 0; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + parser = factory.newDocumentBuilder(); + } } catch (SAXException e) { throw new IFException("SAX error in startDocument()", e); + } catch (ParserConfigurationException pce) { + throw new IFException("Error creating new DocumentBuilder", pce); } } @@ -210,9 +267,33 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (id != null) { atts.addAttribute(XML_NAMESPACE, "id", "xml:id", XMLUtil.CDATA, id); } + handler.startElement(EL_PAGE_SEQUENCE, atts); + if (this.getUserAgent().accessibilityEnabled()) { + if (doc == null) { + doc = parser.parse( + new ByteArrayInputStream(this.getUserAgent().getReducedFOTree())); + } + handler.startElement(EL_STRUCTURE_TREE); // add structure tree + String xpathExpr + = "/fo:root/fo:page-sequence[" + Integer.toString(++pageSequenceCounter) + "]/*"; + XPath xpath = XPathFactory.newInstance().newXPath(); + NamespaceContext namespaceContext + = new NamespaceContextImpl("fo", "http://www.w3.org/1999/XSL/Format"); + xpath.setNamespaceContext(namespaceContext); + NodeList nodes = (NodeList)xpath.evaluate(xpathExpr, doc, XPathConstants.NODESET); + for (int i = 0, n = nodes.getLength(); i < n; i++) { + Node node = nodes.item(i); + new DOM2SAX(handler).writeFragment(node); + } + handler.endElement(EL_STRUCTURE_TREE); + } } catch (SAXException e) { throw new IFException("SAX error in startPageSequence()", e); + } catch (XPathExpressionException e) { + throw new IFException("Error while evaluating XPath expression", e); + } catch (IOException ioe) { + throw new IFException("I/O error while parsing structure tree", ioe); } } @@ -285,6 +366,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler public void startPageTrailer() throws IFException { try { handler.startElement(EL_PAGE_TRAILER); + commitNavigation(); } catch (SAXException e) { throw new IFException("SAX error in startPageTrailer()", e); } @@ -293,7 +375,6 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler /** {@inheritDoc} */ public void endPageTrailer() throws IFException { try { - commitNavigation(); handler.endElement(EL_PAGE_TRAILER); } catch (SAXException e) { throw new IFException("SAX error in endPageTrailer()", e); @@ -382,7 +463,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, XLINK_HREF, uri); @@ -391,6 +472,9 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); + if (ptr != null) { + addAttribute(atts, "ptr", ptr); // used for accessibility + } handler.element(EL_IMAGE, atts); } catch (SAXException e) { throw new IFException("SAX error in startGroup()", e); @@ -409,7 +493,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { try { AttributesImpl atts = new AttributesImpl(); addAttribute(atts, "x", Integer.toString(rect.x)); @@ -417,6 +501,9 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler addAttribute(atts, "width", Integer.toString(rect.width)); addAttribute(atts, "height", Integer.toString(rect.height)); addForeignAttributes(atts); + if (ptr != null) { + addAttribute(atts, "ptr", ptr); // used for accessibility + } handler.startElement(EL_IMAGE, atts); new DOM2SAX(handler).writeDocument(doc, true); handler.endElement(EL_IMAGE); @@ -515,7 +602,7 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler /** {@inheritDoc} */ public void drawText(int x, int y, int letterSpacing, int wordSpacing, - int[] dx, String text) throws IFException { + int[] dx, String text, String ptr) throws IFException { try { AttributesImpl atts = new AttributesImpl(); XMLUtil.addAttribute(atts, XMLConstants.XML_SPACE, "preserve"); @@ -530,6 +617,9 @@ public class IFSerializer extends AbstractXMLWritingIFDocumentHandler if (dx != null) { addAttribute(atts, "dx", IFUtil.toString(dx)); } + if (ptr != null) { + addAttribute(atts, "ptr", ptr); // used for accessibility + } handler.startElement(EL_TEXT, atts); char[] chars = text.toCharArray(); handler.characters(chars, 0, chars.length); diff --git a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java index f396fd09e..8a4237af1 100644 --- a/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java +++ b/src/java/org/apache/fop/render/intermediate/extensions/AbstractAction.java @@ -27,6 +27,7 @@ import org.apache.xmlgraphics.util.XMLizable; public abstract class AbstractAction implements XMLizable { private String id; + private String ptr; // used for accessibility /** * Sets an ID to make the action referencable. @@ -43,7 +44,23 @@ public abstract class AbstractAction implements XMLizable { public String getID() { return this.id; } - + + /** + * Used for accessibility + * @param s representing the ptr + */ + public void setPtr(String s) { + this.ptr = s; + } + + /** + * Used for accessibility + * @return the ptr + */ + public String getPtr() { + return this.ptr; + } + /** * Indicates whether the action has an ID and is therefore referencable. * @return true if the action has an ID diff --git a/src/java/org/apache/fop/render/java2d/Java2DPainter.java b/src/java/org/apache/fop/render/java2d/Java2DPainter.java index 55c5b8015..da3108973 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DPainter.java +++ b/src/java/org/apache/fop/render/java2d/Java2DPainter.java @@ -156,7 +156,7 @@ public class Java2DPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { drawImageUsingURI(uri, rect); } @@ -168,7 +168,7 @@ public class Java2DPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { drawImageUsingDocument(doc, rect); } @@ -208,7 +208,7 @@ public class Java2DPainter 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, String ptr) throws IFException { g2dState.updateColor(state.getTextColor()); FontTriplet triplet = new FontTriplet( diff --git a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java index 933398125..74c709278 100644 --- a/src/java/org/apache/fop/render/java2d/Java2DRenderer.java +++ b/src/java/org/apache/fop/render/java2d/Java2DRenderer.java @@ -875,7 +875,7 @@ public abstract class Java2DRenderer extends AbstractPathOrientedRenderer implem ImageFlavor.XML_DOM}; /** {@inheritDoc} */ - protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes, String ptr) { int x = currentIPPosition + (int)Math.round(pos.getX()); int y = currentBPPosition + (int)Math.round(pos.getY()); diff --git a/src/java/org/apache/fop/render/pcl/PCLPainter.java b/src/java/org/apache/fop/render/pcl/PCLPainter.java index da4f6a656..57810fb38 100644 --- a/src/java/org/apache/fop/render/pcl/PCLPainter.java +++ b/src/java/org/apache/fop/render/pcl/PCLPainter.java @@ -154,7 +154,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { drawImageUsingURI(uri, rect); } @@ -176,7 +176,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { drawImageUsingDocument(doc, rect); } @@ -312,8 +312,9 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { } /** {@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, String ptr) throws IFException { + //Note: ptr is ignored as it is only needed for accessibility try { FontTriplet triplet = new FontTriplet( state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); @@ -474,7 +475,7 @@ public class PCLPainter extends AbstractIFPainter implements PCLConstants { Java2DPainter painter = new Java2DPainter(g2d, getContext(), parent.getFontInfo(), state); try { - painter.drawText(x, y, letterSpacing, wordSpacing, dx, text); + painter.drawText(x, y, letterSpacing, wordSpacing, dx, text, ""); } catch (IFException e) { //This should never happen with the Java2DPainter throw new RuntimeException("Unexpected error while painting text", e); diff --git a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java index 841dd7e01..2f4379795 100644 --- a/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java +++ b/src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java @@ -49,4 +49,6 @@ public interface PDFConfigurationConstants { * PDF/X profile is active). */ String KEY_DISABLE_SRGB_COLORSPACE = "disable-srgb-colorspace"; + /** PDF Accessibility */ + String ACCESSIBLITY = "accessibility"; } diff --git a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java index 45fb7ec51..750f9a3cf 100644 --- a/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java +++ b/src/java/org/apache/fop/render/pdf/PDFContentGenerator.java @@ -50,6 +50,7 @@ public class PDFContentGenerator { /** the current stream to add PDF commands to */ private PDFStream currentStream; + private boolean accessEnabled; // used for accessibility /** drawing state */ protected PDFPaintingState currentState = null; @@ -63,9 +64,10 @@ public class PDFContentGenerator { * @param document the PDF document * @param out the output stream the PDF document is generated to * @param resourceContext the resource context + * @param accessibilityEnabled indicating if accessibility is enabled or not */ public PDFContentGenerator(PDFDocument document, OutputStream out, - PDFResourceContext resourceContext) { + PDFResourceContext resourceContext, boolean accessibilityEnabled) { this.document = document; this.outputStream = out; this.resourceContext = resourceContext; @@ -78,6 +80,7 @@ public class PDFContentGenerator { }; this.currentState = new PDFPaintingState(); + this.accessEnabled = accessibilityEnabled; } /** @@ -153,6 +156,23 @@ public class PDFContentGenerator { currentStream.add("q\n"); } + /** {@inheritDoc} */ + protected void saveGraphicsState(String structElemType, int sequenceNum) { + endTextObject(); + currentState.save(); + startAccessSequence(structElemType, sequenceNum); + currentStream.add("q\n"); + } + + /** + * Used for accessibility + * @param structElemType Structure Element Type + * @param sequenceNum Sequence number + */ + protected void startAccessSequence(String structElemType, int sequenceNum) { + currentStream.add(structElemType + " <</MCID " + String.valueOf(sequenceNum) + ">>\nBDC\n"); + } + /** * Restored the graphics state valid before the previous {@code #saveGraphicsState()}. * @param popState true if the state should also be popped, false if only the PDF command @@ -171,6 +191,35 @@ public class PDFContentGenerator { restoreGraphicsState(true); } + /** used for accessibility */ + protected void restoreGraphicsStateAccess() { + endTextObject(); + currentStream.add("Q\n"); + currentStream.add("EMC\n"); + currentState.restore(); + } + + /** + * used for accessibility, separates 2 text elements + * @param mcid of new text element + * @param structElemType of parent of new text element + */ + protected void separateTextElements(int mcid, String structElemType) { + textutil.endTextObject(true); + textutil.beginTextObjectAccess(mcid, structElemType); + } + + /** + * used for accessibility + * separates a text element from fo:leader text element + */ + public void separateTextElementFromLeader() { + if (!textutil.inArtifactMode()) { + textutil.endTextObject(true); + textutil.beginArtifactTextObject(); + } + } + /** Indicates the beginning of a text object. */ protected void beginTextObject() { if (!textutil.isInTextObject()) { @@ -178,10 +227,30 @@ public class PDFContentGenerator { } } + /** + * Accessibility beginTextObject + * @param mcid of text element + * @param structElemType of parent + */ + protected void beginTextObjectAccess(int mcid, String structElemType) { + if (!textutil.isInTextObject()) { + textutil.beginTextObjectAccess(mcid, structElemType); + } + } + + /** + * Accessibility begin of LeaderTextObject + */ + public void beginLeaderTextObject() { + if (!textutil.isInTextObject()) { + textutil.beginArtifactTextObject(); + } + } + /** Indicates the end of a text object. */ protected void endTextObject() { if (textutil.isInTextObject()) { - textutil.endTextObject(); + textutil.endTextObject(accessEnabled); } } @@ -326,5 +395,26 @@ public class PDFContentGenerator { restoreGraphicsState(); } + /** + * Places a previously registered image at a certain place on the page. + * Accessibility version + * @param x X coordinate + * @param y Y coordinate + * @param w width for image + * @param h height for image + * @param xobj the image XObject + * @param structElemType of this image + * @param mcid of this image + */ + public void placeImage(float x, float y, float w, float h, PDFXObject xobj, + String structElemType, int mcid) { + saveGraphicsState(structElemType, mcid); + add(format(w) + " 0 0 " + + format(-h) + " " + + format(x) + " " + + format(y + h) + + " cm\n" + xobj.getName() + " Do\n"); + restoreGraphicsStateAccess(); + } } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java index b98b15d5e..f7eb3e7b3 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentHandler.java @@ -21,9 +21,28 @@ package org.apache.fop.render.pdf; import java.awt.Dimension; import java.awt.geom.AffineTransform; +import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import javax.xml.namespace.NamespaceContext; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.xpath.XPath; +import javax.xml.xpath.XPathConstants; +import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFactory; + +import org.w3c.dom.Document; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; + +import org.xml.sax.SAXException; + import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -32,11 +51,19 @@ import org.apache.xmlgraphics.xmp.Metadata; import org.apache.fop.apps.MimeConstants; import org.apache.fop.fo.extensions.xmp.XMPMetadata; import org.apache.fop.pdf.PDFAnnotList; +import org.apache.fop.pdf.PDFArray; +import org.apache.fop.pdf.PDFDictionary; import org.apache.fop.pdf.PDFDocument; +import org.apache.fop.pdf.PDFName; +import org.apache.fop.pdf.PDFNumsArray; +import org.apache.fop.pdf.PDFObject; import org.apache.fop.pdf.PDFPage; +import org.apache.fop.pdf.PDFParentTree; import org.apache.fop.pdf.PDFReference; import org.apache.fop.pdf.PDFResourceContext; import org.apache.fop.pdf.PDFResources; +import org.apache.fop.pdf.PDFStructElem; +import org.apache.fop.pdf.PDFStructTreeRoot; import org.apache.fop.render.intermediate.AbstractBinaryWritingIFDocumentHandler; import org.apache.fop.render.intermediate.IFContext; import org.apache.fop.render.intermediate.IFDocumentHandlerConfigurator; @@ -52,6 +79,70 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** logging instance */ private static Log log = LogFactory.getLog(PDFDocumentHandler.class); + /** the following variables are used for accessibility */ + private int pageSequenceCounter; + private DocumentBuilder parser = null; + private Document doc = null; + private Map structElemType = new HashMap(); + private boolean accessEnabled = false; + private int parentTreeKey = -1; + private int pageLinkCount = 0; + private int mcidKey = -1; + private PDFParentTree parentTree = null; + private Map structTreeMap = new HashMap(); + private List parentTreeList = new java.util.ArrayList(); + + private static class NamespaceContextImpl implements NamespaceContext { + + private String uri; + private String prefix; + + public NamespaceContextImpl() { + } + + public NamespaceContextImpl(String prefix, String uri) { + this.uri = uri; + this.prefix = prefix; + } + + public String getNamespaceURI(String prefix) { + return uri; + } + + public void setNamespaceURI(String uri) { + this.uri = uri; + } + + public String getPrefix(String uri) { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public Iterator getPrefixes(String uri) { + return null; + } + + } + + private static final class ParentTreeEntry { + private final int position; + private final PDFObject object; + private ParentTreeEntry(int p, PDFObject o) { + position = p; + object = o; + } + private int getPosition() { + return position; + } + private PDFObject getPDFObject() { + return object; + } + } + + /** the PDF Document being created */ protected PDFDocument pdfDoc; @@ -79,7 +170,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); /** @@ -90,7 +181,7 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public boolean supportsPagesOutOfOrder() { - return true; + return !accessEnabled; } /** {@inheritDoc} */ @@ -123,8 +214,21 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { super.startDocument(); try { this.pdfDoc = pdfUtil.setupPDFDocument(this.outputStream); + this.accessEnabled = getUserAgent().accessibilityEnabled(); + if (accessEnabled) { + //TODO: make document language variable, see note on wiki page PDF Accessibility + //TODO: and follow-up emails on fop-dev + this.pdfDoc.getRoot().setLanguage("en"); + parentTree = new PDFParentTree(); + pageSequenceCounter = 0; + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setNamespaceAware(true); + parser = factory.newDocumentBuilder(); + } } catch (IOException e) { throw new IFException("I/O error in startDocument()", e); + } catch (ParserConfigurationException pce) { + throw new IFException("Error creating new DocumentBuilder", pce); } } @@ -137,10 +241,34 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { public void endDocument() throws IFException { try { pdfDoc.getResources().addFonts(pdfDoc, fontInfo); - pdfDoc.outputTrailer(this.outputStream); - + if (getUserAgent().accessibilityEnabled()) { + PDFNumsArray nums = parentTree.getNums(); + for (int i = 0; i <= this.parentTreeKey; i++) { + PDFArray tArray = new PDFArray(); + for (int j = 0; j < parentTreeList.size(); j++) { + if (((ParentTreeEntry)parentTreeList.get(j)).getPosition() == i) { + tArray.add(((ParentTreeEntry)parentTreeList.get(j)).getPDFObject()); + } + } + if (tArray.length() == 1) { + nums.put(i, tArray.get(0)); + } else if (tArray.length() > 1) { + nums.put(i, tArray); + } + } + parentTree.setNums(nums); + getStructTreeRoot().addParentTree(parentTree); + pdfDoc.outputTrailer(this.outputStream); + parser = null; + doc = null; + structElemType = null; + parentTree = null; + structTreeMap = null; + parentTreeList = null; + } else { + pdfDoc.outputTrailer(this.outputStream); + } this.pdfDoc = null; - pdfResources = null; this.generator = null; currentContext = null; @@ -151,9 +279,60 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { super.endDocument(); } + private PDFStructTreeRoot getStructTreeRoot() { + return this.pdfDoc.getRoot().getStructTreeRoot(); + } + + /** {@inheritDoc} */ public void startPageSequence(String id) throws IFException { //TODO page sequence title, country and language + + if (getUserAgent().accessibilityEnabled()) { + try { + if (doc == null) { + doc = parser.parse( + new ByteArrayInputStream(this.getUserAgent().getReducedFOTree())); + } + PDFStructElem parent = (PDFStructElem)getStructTreeRoot().getFirstChild(); + PDFStructElem structElemPart = new PDFStructElem("page-sequence", parent); + this.pdfDoc.assignObjectNumber(structElemPart); + this.pdfDoc.addTrailerObject(structElemPart); + parent.addKid(structElemPart); + + String xpathExpr = "/fo:root/fo:page-sequence[" + + Integer.toString(++pageSequenceCounter) + "]/*"; + XPath xpath = XPathFactory.newInstance().newXPath(); + NamespaceContext namespaceContext = new NamespaceContextImpl("fo", + "http://www.w3.org/1999/XSL/Format"); + xpath.setNamespaceContext(namespaceContext); + + NodeList nodes = (NodeList) xpath.evaluate(xpathExpr, doc, + XPathConstants.NODESET); + + for (int i = 0, n = nodes.getLength(); i < n; i++) { + Node node = nodes.item(i); + if (node.getNodeName().equals("fo:flow") + || node.getNodeName().equals("fo:static-content")) { + PDFStructElem structElemSect = new PDFStructElem( + node.getLocalName(), structElemPart); + this.pdfDoc.assignObjectNumber(structElemSect); + this.pdfDoc.addTrailerObject(structElemSect); + structElemPart.addKid(structElemSect); + NodeList iNodes = node.getChildNodes(); + for (int j = 0, m = iNodes.getLength(); j < m; j++) { + processContent(iNodes.item(j), structElemSect, 1); + } + } + } + } catch (SAXException e) { + throw new IFException("SAX error in startPageSequence()", e); + } catch (XPathExpressionException e) { + throw new IFException("Error while evaluating XPath expression", e); + } catch (IOException ioe) { + throw new IFException("I/O error while parsing structure tree", ioe); + } + } } /** {@inheritDoc} */ @@ -164,22 +343,25 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { /** {@inheritDoc} */ public void startPage(int index, String name, String pageMasterName, Dimension size) throws IFException { + // used for accessibility + this.parentTreeKey = this.parentTreeKey + this.pageLinkCount + 1; + this.mcidKey = 0; + this.pageLinkCount = 0; + // this.pdfResources = this.pdfDoc.getResources(); this.currentPage = this.pdfDoc.getFactory().makePage( this.pdfResources, (int)Math.round(size.getWidth() / 1000), - (int)Math.round(size.getHeight() / 1000), - index); - //pageReferences.put(new Integer(index)/*page.getKey()*/, currentPage.referencePDF()); - //pvReferences.put(page.getKey(), page); - + (int)Math.round(size.getHeight() / 1000), index, + parentTreeKey); // used for accessibility 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, this.accessEnabled); // 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, size.height / 1000f); @@ -234,8 +416,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(); @@ -251,4 +433,191 @@ public class PDFDocumentHandler extends AbstractBinaryWritingIFDocumentHandler { } } + /** + * Used for accessibility + * @param position in parentTree + * @param o reference of PDFObject to be added to parentTree + */ + void addToParentTree(int position, PDFObject o) { + PDFNumsArray nums = parentTree.getNums(); + nums.put(position, o); + parentTree.setNums(nums); + } + + + /** + * Used for accessibility + * @param position in parentTree + * @param o object to be added to parentTree + */ + void addToTempList(int position, PDFObject o) { + ParentTreeEntry myEntry = new ParentTreeEntry(position, o); + this.parentTreeList.add(myEntry); + } + + + /** + * Return the PDFObject + * @param ptr this is the key + * @return PDFObject referenced with ptr + */ + PDFObject getTrailerObject(String ptr) { + return (PDFObject) this.structTreeMap.get(ptr); + } + + /** + * Return the parent PDFObject referenced by ptr + * @param ptr this is the key + * @return PDFObject parent of PDFObject referenced with ptr + */ + PDFObject getParentTrailerObject(String ptr) { + PDFStructElem tempStructElem = (PDFStructElem) this.structTreeMap.get(ptr); + return tempStructElem.getParentStructElem(); + } + + /** + * Adds a link object as child to StructElem + * @param ptr of PDFStructElem + * @param o PDFLink object + */ + void addLinkToStructElem(String ptr, PDFObject o) { + PDFDictionary dict = new PDFDictionary(); + dict.put("Type", new PDFName("OBJR")); + dict.put("Pg", this.currentPage); + dict.put("Obj", o); + PDFStructElem tempStructElem = (PDFStructElem) structTreeMap.get(ptr); + tempStructElem.addKid(dict); + } + + /** + * Adds a child to StructElem, called from PDFPainter.drawImage + * @param ptr of PDFStructElem + * @param mcid sequence number within page + */ + void addChildToStructElemImage(String ptr, int mcid) { + PDFStructElem tempStructElem = (PDFStructElem) structTreeMap.get(ptr); + tempStructElem.addMCIDKid(mcid); + tempStructElem.addPage(this.currentPage); + if (!tempStructElem.getLevel1()) { + addMeToParent(tempStructElem); + } + } + + /** + * Adds a child to StructElem, called from PDFPainter.drawText + * @param ptr of PDFSturctElem + * @param mcid sequence number within page + */ + void addChildToStructElemText(String ptr, int mcid) { + PDFDictionary dict = new PDFDictionary(); + dict.put("Type", new PDFName("MCR")); + dict.put("Pg", this.currentPage); + dict.put("MCID", mcid); + PDFStructElem tempStructElem = (PDFStructElem) structTreeMap.get(ptr); + tempStructElem.addKid(dict); + if (!tempStructElem.getLevel1()) { + addMeToParent(tempStructElem); + } + } + + /** + * Add child PDFStructElem to parent child elements + * Repeat until level 1 or child already exists + * @param childStructElem to be added + */ + protected void addMeToParent(PDFStructElem childStructElem) { + PDFStructElem parentStructElem = (PDFStructElem) childStructElem.getParentStructElem(); + // test if child already exists or not + if (parentStructElem.addUniqueKid(childStructElem)) { + if (!parentStructElem.getLevel1()) { + addMeToParent(parentStructElem); + } + } + } + + /** + * increment MCID value + */ + void incMCID() { + this.mcidKey++; + } + + /** + * MCID is a sequential number per page + * @return MCID value + */ + int getMCID() { + return this.mcidKey; + } + + /** + * Used for accessibility + * @param ptr pointer into map of all structElems + * @return type of found structElem + */ + String getStructElemType(String ptr) { + return (String) structElemType.get(ptr); + } + + /** + * Used for accessibility + * @param me node being processed + * @param parent parent node in DOM of me + * @param depth depth level in DOM, static-content & flow are 0 + */ + private void processContent(Node me, PDFStructElem parent, int depth) { + String ptr; + Node attr = me.getAttributes().getNamedItem("foi:ptr"); + if (attr != null) { + ptr = attr.getNodeValue(); + } else { + log.error("Accessibility: missing foi:ptr"); + ptr = ""; + } + String s = me.getLocalName(); + PDFStructElem structElem = new PDFStructElem(s, parent); + this.pdfDoc.assignObjectNumber(structElem); + this.pdfDoc.addTrailerObject(structElem); + if (depth == 1) { + parent.addKid(structElem); + structElem.setLevel1(); + } + if (s.equals("external-graphic") || s.equals("instream-foreign-object")) { + Node altTextNode = me.getAttributes().getNamedItem("fox:alt-text"); + if (altTextNode != null) { + structElem.put("Alt", altTextNode.getNodeValue()); + } else { + log.warn("fo:" + s + + " requires an alternative text attribute fox:alt-text for accessibility"); + structElem.put("Alt", "No alternate text specified"); + } + } + // the following map is used e.g. in PDFPainter.drawText + structElemType.put(ptr, structElem.get("S").toString()); + // this map will be used for fast access of the StructElem by ptr + structTreeMap.put(ptr, structElem); + NodeList nodes = me.getChildNodes(); + depth++; + for (int i = 0, n = nodes.getLength(); i < n; i++) { + processContent(nodes.item(i), structElem, depth); + } + } + + /** + * used for accessibility + * @return mcid to be used for next link to be processed + */ + int getPageLinkCountPlusPageParentKey() { + this.pageLinkCount++; + return (this.parentTreeKey + this.pageLinkCount); + } + + /** + * used for accessibility + * @return current parentTreeKey + */ + int getCurrentParentTreeKey() { + return this.parentTreeKey; + } + } diff --git a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java index bc22bb6d2..2f2de2e62 100644 --- a/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java +++ b/src/java/org/apache/fop/render/pdf/PDFDocumentNavigationHandler.java @@ -42,15 +42,18 @@ import org.apache.fop.render.intermediate.extensions.NamedDestination; import org.apache.fop.render.intermediate.extensions.URIAction; import org.apache.fop.render.pdf.PDFDocumentHandler.PageReference; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + /** * Implementation of the {@link IFDocumentNavigationHandler} interface for PDF output. */ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler { + private static Log log = LogFactory.getLog(PDFDocumentHandler.class); + private final PDFDocumentHandler documentHandler; - private PDFDocumentHandler documentHandler; - - private Map incompleteActions = new java.util.HashMap(); - private Map completeActions = new java.util.HashMap(); + private final Map incompleteActions = new java.util.HashMap(); + private final Map completeActions = new java.util.HashMap(); /** * Default constructor. @@ -111,6 +114,14 @@ public class PDFDocumentNavigationHandler implements IFDocumentNavigationHandler PDFLink pdfLink = getPDFDoc().getFactory().makeLink( targetRect2D, pdfAction); if (pdfLink != null) { + //accessibility: ptr has a value + String ptr = link.getAction().getPtr(); + if (ptr.length() > 0) { + this.documentHandler.addLinkToStructElem(ptr, pdfLink); + int id = this.documentHandler.getPageLinkCountPlusPageParentKey(); + pdfLink.setStructParent(id); + this.documentHandler.addToParentTree(id, pdfLink ); + } documentHandler.currentPage.addAnnotation(pdfLink); } } diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java index d47d5a439..074a19bec 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRawJPEG.java @@ -82,7 +82,17 @@ public class PDFImageHandlerRawJPEG implements PDFImageHandler, ImageHandler { float y = (float)pos.getY() / 1000f; float w = (float)pos.getWidth() / 1000f; float h = (float)pos.getHeight() / 1000f; - generator.placeImage(x, y, w, h, xobj); + if (context.getUserAgent().accessibilityEnabled()) { + String structElemType = pdfContext.getStructElemType(); + if (structElemType.length() > 0) { + int sequenceNum = pdfContext.getSequenceNum(); + generator.placeImage(x, y, w, h, xobj, structElemType, sequenceNum); + } else { + generator.placeImage(x, y, w, h, xobj); + } + } else { + generator.placeImage(x, y, w, h, xobj); + } } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java index 3e57c7216..728fa0601 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerRenderedImage.java @@ -83,7 +83,13 @@ public class PDFImageHandlerRenderedImage implements PDFImageHandler, ImageHandl float y = (float)pos.getY() / 1000f; float w = (float)pos.getWidth() / 1000f; float h = (float)pos.getHeight() / 1000f; - generator.placeImage(x, y, w, h, xobj); + if (context.getUserAgent().accessibilityEnabled()) { + String structElemType = pdfContext.getStructElemType(); + int sequenceNum = pdfContext.getSequenceNum(); + generator.placeImage(x, y, w, h, xobj, structElemType, sequenceNum); + } else { + generator.placeImage(x, y, w, h, xobj); + } } /** {@inheritDoc} */ diff --git a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java index d1b7aa986..40fabbc5b 100644 --- a/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java +++ b/src/java/org/apache/fop/render/pdf/PDFImageHandlerSVG.java @@ -101,8 +101,8 @@ public class PDFImageHandlerSVG implements ImageHandler { float w = (float)ctx.getDocumentSize().getWidth() * 1000f; float h = (float)ctx.getDocumentSize().getHeight() * 1000f; - float sx = pos.width / (float)w; - float sy = pos.height / (float)h; + float sx = pos.width / w; + float sy = pos.height / h; //Scaling and translation for the bounding box of the image AffineTransform scaling = new AffineTransform( @@ -121,6 +121,11 @@ public class PDFImageHandlerSVG implements ImageHandler { */ generator.comment("SVG setup"); generator.saveGraphicsState(); + if (context.getUserAgent().accessibilityEnabled()) { + String structElemType = pdfContext.getStructElemType(); + int sequenceNum = pdfContext.getSequenceNum(); + generator.startAccessSequence(structElemType, sequenceNum); + } generator.setColor(Color.black, false); generator.setColor(Color.black, true); @@ -168,7 +173,11 @@ public class PDFImageHandlerSVG implements ImageHandler { eventProducer.svgRenderingError(this, e, image.getInfo().getOriginalURI()); } generator.getState().restore(); - generator.restoreGraphicsState(); + if (context.getUserAgent().accessibilityEnabled()) { + generator.restoreGraphicsStateAccess(); + } else { + generator.restoreGraphicsState(); + } generator.comment("SVG end"); } diff --git a/src/java/org/apache/fop/render/pdf/PDFPainter.java b/src/java/org/apache/fop/render/pdf/PDFPainter.java index fa00fd7b4..b87569ed3 100644 --- a/src/java/org/apache/fop/render/pdf/PDFPainter.java +++ b/src/java/org/apache/fop/render/pdf/PDFPainter.java @@ -59,12 +59,18 @@ public class PDFPainter extends AbstractIFPainter { /** logging instance */ private static Log log = LogFactory.getLog(PDFPainter.class); - private PDFDocumentHandler documentHandler; + private final PDFDocumentHandler documentHandler; /** The current content generator */ protected PDFContentGenerator generator; - private PDFBorderPainter borderPainter; + private final PDFBorderPainter borderPainter; + + private boolean accessEnabled = false; + + private int mcid; // used for accessibility + + private String structElemType; // used for accessibility /** * Default constructor. @@ -76,6 +82,7 @@ public class PDFPainter extends AbstractIFPainter { this.generator = documentHandler.generator; this.borderPainter = new PDFBorderPainter(this.generator); this.state = IFState.create(); + accessEnabled = this.getUserAgent().accessibilityEnabled(); } /** {@inheritDoc} */ @@ -122,15 +129,50 @@ public class PDFPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) + throws IFException { PDFXObject xobject = getPDFDoc().getXObject(uri); if (xobject != null) { - placeImage(rect, xobject); + if (accessEnabled && ptr.length() > 0) { + mcid = this.documentHandler.getMCID(); + mcid++; // fix for Acro Checker + this.documentHandler.incMCID(); // simulating a parent text element + structElemType = this.documentHandler.getStructElemType(ptr); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getParentTrailerObject(ptr)); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + placeImageAccess(rect, xobject); + this.documentHandler.addChildToStructElemImage(ptr, mcid); + this.documentHandler.incMCID(); + } else { + placeImage(rect, xobject); + } return; } - - drawImageUsingURI(uri, rect); - + if (accessEnabled && ptr.length() > 0) { + mcid = this.documentHandler.getMCID(); + mcid++; // fix for Acro Checker + this.documentHandler.incMCID(); // simulating a parent text element + structElemType = this.documentHandler.getStructElemType(ptr); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getParentTrailerObject(ptr)); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + //PDFRenderingContext pdfContext = new PDFRenderingContext( + // getUserAgent(), generator, this.documentHandler.currentPage, getFontInfo()); + //pdfContext.setMCID(mcid); + //pdfContext.setStructElemType(structElemType); + drawImageUsingURI(uri, rect); + this.documentHandler.addChildToStructElemImage(ptr, mcid); + this.documentHandler.incMCID(); + } else { + drawImageUsingURI(uri, rect); + } flushPDFDoc(); } @@ -138,6 +180,8 @@ public class PDFPainter extends AbstractIFPainter { protected RenderingContext createRenderingContext() { PDFRenderingContext pdfContext = new PDFRenderingContext( getUserAgent(), generator, this.documentHandler.currentPage, getFontInfo()); + pdfContext.setMCID(mcid); + pdfContext.setStructElemType(structElemType); return pdfContext; } @@ -158,11 +202,43 @@ public class PDFPainter extends AbstractIFPainter { + " cm " + xobj.getName() + " Do\n"); generator.restoreGraphicsState(); } + /** + * Places a previously registered image at a certain place on the page - Accessibility version + * @param x X coordinate + * @param y Y coordinate + * @param w width for image + * @param h height for image + * @param xobj the image XObject + */ + private void placeImageAccess(Rectangle rect, PDFXObject xobj) { + generator.saveGraphicsState(structElemType, mcid); + generator.add(format(rect.width) + " 0 0 " + + format(-rect.height) + " " + + format(rect.x) + " " + + format(rect.y + rect.height ) + + " cm " + xobj.getName() + " Do\n"); + generator.restoreGraphicsStateAccess(); + } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { - drawImageUsingDocument(doc, rect); - + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { + if (accessEnabled && ptr.length() > 0) { + mcid = this.documentHandler.getMCID(); + mcid++; // fix for Acro Checker + this.documentHandler.incMCID(); // simulating a parent text element + structElemType = this.documentHandler.getStructElemType(ptr); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getParentTrailerObject(ptr)); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + drawImageUsingDocument(doc, rect); + this.documentHandler.addChildToStructElemImage(ptr, mcid); + this.documentHandler.incMCID(); + } else { + drawImageUsingDocument(doc, rect); + } flushPDFDoc(); } @@ -253,10 +329,38 @@ 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, String ptr) throws IFException { - generator.updateColor(state.getTextColor(), true, null); - generator.beginTextObject(); + if (accessEnabled ) { + int mcId; + String structElType = ""; + if (ptr != null && ptr.length() > 0) { + mcId = this.documentHandler.getMCID(); + this.documentHandler.addToTempList( + this.documentHandler.getCurrentParentTreeKey(), + this.documentHandler.getTrailerObject(ptr)); + structElType = this.documentHandler.getStructElemType(ptr); + if (generator.getTextUtil().isInTextObject()) { + generator.separateTextElements(mcId, structElType); + } + generator.updateColor(state.getTextColor(), true, null); + generator.beginTextObjectAccess(mcId, structElType); + this.documentHandler.addChildToStructElemText(ptr, mcId); + this.documentHandler.incMCID(); + } else { + // <fo:leader leader-pattern="use-content"> + if (generator.getTextUtil().isInTextObject()) { + generator.separateTextElementFromLeader(); + } + generator.updateColor(state.getTextColor(), true, null); + generator.beginLeaderTextObject(); + } + } else { + generator.updateColor(state.getTextColor(), true, null); + generator.beginTextObject(); + } + FontTriplet triplet = new FontTriplet( state.getFontFamily(), state.getFontStyle(), state.getFontWeight()); //TODO Ignored: state.getFontVariant() @@ -277,7 +381,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 c40c94fc4..c6b4d4977 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderer.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderer.java @@ -461,7 +461,8 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf double h = bounds.getHeight(); pageHeight = (int) h; - this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage); + this.generator = new PDFContentGenerator(this.pdfDoc, this.ostream, this.currentPage, + false); this.borderPainter = new PDFBorderPainter(this.generator); // Transform the PDF's default coordinate system (0,0 at lower left) to the PDFRenderer's @@ -1073,7 +1074,7 @@ public class PDFRenderer extends AbstractPathOrientedRenderer implements PDFConf } /** {@inheritDoc} */ - protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes, String ptr) { endTextObject(); putImage(url, pos, foreignAttributes); } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java index 98b0c8203..f82019b4a 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingContext.java @@ -34,6 +34,11 @@ public class PDFRenderingContext extends AbstractRenderingContext { private PDFContentGenerator generator; private FontInfo fontInfo; private PDFPage page; + /** Temp. val. for accessibility, used in PDFImageHandlerRenderedImage */ + private String structElemType = ""; + + /** Temp. val. for accessibility, used in PDFImageHandlerRenderedImage */ + private int mcid = -1; /** * Main constructor. @@ -79,4 +84,35 @@ public class PDFRenderingContext extends AbstractRenderingContext { return this.fontInfo; } + /** + * Used for accessibility, used in PDFPainter.drawImage + * @param value to be stored + */ + public void setMCID(int value) { + mcid = value; + } + + /** + * Used for accessibility + * @return mcid + */ + public int getSequenceNum() { + return mcid; + } + + /** + * Used for accessibility + * @param s the type of the structure element + */ + public void setStructElemType(String s) { + structElemType = s; + } + + /** + * Used for accessibility + * @return the type of the structure element + */ + public String getStructElemType() { + return structElemType; + } } diff --git a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java index 2e3c83126..8d9536bff 100644 --- a/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java +++ b/src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java @@ -52,6 +52,8 @@ import org.apache.fop.pdf.PDFMetadata; import org.apache.fop.pdf.PDFNumsArray; import org.apache.fop.pdf.PDFOutputIntent; import org.apache.fop.pdf.PDFPageLabels; +import org.apache.fop.pdf.PDFStructElem; +import org.apache.fop.pdf.PDFStructTreeRoot; import org.apache.fop.pdf.PDFXMode; import org.apache.fop.util.ColorProfileUtil; @@ -75,6 +77,9 @@ class PDFRenderingUtil implements PDFConfigurationConstants { /** the PDF/X mode (Default: disabled) */ protected PDFXMode pdfXMode = PDFXMode.DISABLED; + /** the accessibility mode (Default: false=disabled) */ + protected boolean accessibility = false; + /** the (optional) encryption parameters */ protected PDFEncryptionParams encryptionParams; @@ -169,6 +174,12 @@ class PDFRenderingUtil implements PDFConfigurationConstants { if (s != null) { this.outputProfileURI = s; } + // used for accessibility + setting = userAgent.getRendererOptions().get(ACCESSIBLITY); + if (setting != null) { + this.accessibility = booleanValueOf(setting); + } + setting = userAgent.getRendererOptions().get(KEY_DISABLE_SRGB_COLORSPACE); if (setting != null) { this.disableSRGBColorSpace = booleanValueOf(setting); @@ -384,6 +395,16 @@ class PDFRenderingUtil implements PDFConfigurationConstants { log.debug("PDF/A is active. Conformance Level: " + pdfAMode); addPDFA1OutputIntent(); } + if (this.accessibility) { + this.pdfDoc.getRoot().makeTagged(); + log.info("Accessibility is enabled"); + PDFStructTreeRoot structTreeRoot = this.pdfDoc.getFactory().makeStructTreeRoot(); + this.pdfDoc.getRoot().setStructTreeRoot(structTreeRoot); + PDFStructElem structElemDocument = new PDFStructElem("root", structTreeRoot); + this.pdfDoc.assignObjectNumber(structElemDocument); + this.pdfDoc.addTrailerObject(structElemDocument); + structTreeRoot.addKid(structElemDocument); + } return this.pdfDoc; } diff --git a/src/java/org/apache/fop/render/ps/PSPainter.java b/src/java/org/apache/fop/render/ps/PSPainter.java index cb88f4670..807bf50b4 100644 --- a/src/java/org/apache/fop/render/ps/PSPainter.java +++ b/src/java/org/apache/fop/render/ps/PSPainter.java @@ -176,7 +176,7 @@ public class PSPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(String uri, Rectangle rect) throws IFException { + public void drawImage(String uri, Rectangle rect, String ptr) throws IFException { try { endTextObject(); } catch (IOException ioe) { @@ -186,7 +186,7 @@ public class PSPainter extends AbstractIFPainter { } /** {@inheritDoc} */ - public void drawImage(Document doc, Rectangle rect) throws IFException { + public void drawImage(Document doc, Rectangle rect, String ptr) throws IFException { try { endTextObject(); } catch (IOException ioe) { @@ -338,7 +338,7 @@ public class PSPainter extends AbstractIFPainter { /** {@inheritDoc} */ public void drawText(int x, int y, int letterSpacing, int wordSpacing, - int[] dx, String text) throws IFException { + int[] dx, String text, String ptr) throws IFException { try { //Note: dy is currently ignored PSGenerator generator = getGenerator(); diff --git a/src/java/org/apache/fop/render/ps/PSRenderer.java b/src/java/org/apache/fop/render/ps/PSRenderer.java index 19fcd8af8..5b918156b 100644 --- a/src/java/org/apache/fop/render/ps/PSRenderer.java +++ b/src/java/org/apache/fop/render/ps/PSRenderer.java @@ -345,7 +345,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer } /** {@inheritDoc} */ - protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String uri, Rectangle2D pos, Map foreignAttributes, String ptr) { endTextObject(); int x = currentIPPosition + (int)Math.round(pos.getX()); int y = currentBPPosition + (int)Math.round(pos.getY()); @@ -1233,7 +1233,7 @@ public class PSRenderer extends AbstractPathOrientedRenderer * {@inheritDoc} */ public void renderImage(Image image, Rectangle2D pos) { - drawImage(image.getURL(), pos, image.getForeignAttributes()); + drawImage(image.getURL(), pos, image.getForeignAttributes(), ""); } /** diff --git a/src/java/org/apache/fop/render/txt/TXTRenderer.java b/src/java/org/apache/fop/render/txt/TXTRenderer.java index 575f1232f..2170a67d2 100644 --- a/src/java/org/apache/fop/render/txt/TXTRenderer.java +++ b/src/java/org/apache/fop/render/txt/TXTRenderer.java @@ -443,7 +443,7 @@ public class TXTRenderer extends AbstractPathOrientedRenderer { } /** {@inheritDoc} */ - protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes) { + protected void drawImage(String url, Rectangle2D pos, Map foreignAttributes, String ptr) { //No images are painted here } |