]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
FOP-2488: Support PDF/UA
authorSimon Steiner <ssteiner@apache.org>
Tue, 16 Jun 2015 11:46:50 +0000 (11:46 +0000)
committerSimon Steiner <ssteiner@apache.org>
Tue, 16 Jun 2015 11:46:50 +0000 (11:46 +0000)
git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1685770 13f79535-47bb-0310-9956-ffa450edef68

17 files changed:
lib/xmlgraphics-commons-svn-trunk.jar
src/java/org/apache/fop/accessibility/fo/StructureTreeEventTrigger.java
src/java/org/apache/fop/apps/FOUserAgent.java
src/java/org/apache/fop/fo/flow/BasicLink.java
src/java/org/apache/fop/pdf/PDFMetadata.java
src/java/org/apache/fop/pdf/PDFProfile.java
src/java/org/apache/fop/pdf/PDFRoot.java
src/java/org/apache/fop/pdf/PDFStructElem.java
src/java/org/apache/fop/pdf/PDFUAMode.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/PDFPainter.java
src/java/org/apache/fop/render/pdf/PDFRendererConfig.java
src/java/org/apache/fop/render/pdf/PDFRendererOption.java
src/java/org/apache/fop/render/pdf/PDFRendererOptionsConfig.java
src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
src/java/org/apache/fop/render/pdf/PDFStructureTreeBuilder.java
test/java/org/apache/fop/fo/complete_document.fo
test/java/org/apache/fop/pdf/PDFUATestCase.java [new file with mode: 0644]

index e7470c8b7c28f480eae0a80338524a56cfe59582..45f5937082e02c029d1fe611d328b70f0e8ab70f 100644 (file)
Binary files a/lib/xmlgraphics-commons-svn-trunk.jar and b/lib/xmlgraphics-commons-svn-trunk.jar differ
index 6b8c048aec34938b36a171b0cd6d4762686171ba..8def8a4d440fe1d6ee4e3493470d04e7cc89243e 100644 (file)
@@ -33,9 +33,9 @@ import org.apache.fop.accessibility.StructureTreeEventHandler;
 import org.apache.fop.fo.FOEventHandler;
 import org.apache.fop.fo.FONode;
 import org.apache.fop.fo.FOText;
+import org.apache.fop.fo.FObj;
 import org.apache.fop.fo.extensions.ExtensionElementMapping;
 import org.apache.fop.fo.extensions.InternalElementMapping;
-import org.apache.fop.fo.flow.AbstractGraphics;
 import org.apache.fop.fo.flow.AbstractRetrieveMarker;
 import org.apache.fop.fo.flow.BasicLink;
 import org.apache.fop.fo.flow.Block;
@@ -384,7 +384,7 @@ class StructureTreeEventTrigger extends FOEventHandler {
 
     @Override
     public void startLink(BasicLink basicLink) {
-        startElementWithID(basicLink);
+        startElementWithIDAndAltText(basicLink, basicLink.getAltText());
     }
 
     @Override
@@ -394,13 +394,13 @@ class StructureTreeEventTrigger extends FOEventHandler {
 
     @Override
     public void image(ExternalGraphic eg) {
-        startElementWithIDAndAltText(eg);
+        startElementWithIDAndAltText(eg, eg.getAltText());
         endElement(eg);
     }
 
     @Override
     public void startInstreamForeignObject(InstreamForeignObject ifo) {
-        startElementWithIDAndAltText(ifo);
+        startElementWithIDAndAltText(ifo, ifo.getAltText());
     }
 
     @Override
@@ -523,12 +523,12 @@ class StructureTreeEventTrigger extends FOEventHandler {
                         node.getParent().getStructureTreeElement()));
     }
 
-    private void startElementWithIDAndAltText(AbstractGraphics node) {
+    private void startElementWithIDAndAltText(FObj node, String altText) {
         AttributesImpl attributes = new AttributesImpl();
         String localName = node.getLocalName();
-        addRole(node, attributes);
+        addRole((CommonAccessibilityHolder)node, attributes);
         addAttribute(attributes, ExtensionElementMapping.URI, "alt-text",
-                ExtensionElementMapping.STANDARD_PREFIX, node.getAltText());
+                ExtensionElementMapping.STANDARD_PREFIX, altText);
         node.setStructureTreeElement(
                 structureTreeEventHandler.startImageNode(localName, attributes,
                         node.getParent().getStructureTreeElement()));
index 239261b843a3220a7ff09be10bf14c7df0a84db9..dca7def7bd11c76b7f5c54110fb773628b29068d 100644 (file)
@@ -106,6 +106,7 @@ public class FOUserAgent {
     private EventBroadcaster eventBroadcaster = new FOPEventBroadcaster();
     private StructureTreeEventHandler structureTreeEventHandler
             = DummyStructureTreeEventHandler.INSTANCE;
+    private boolean pdfUAEnabled;
 
     /** Producer:  Metadata element for the system/software that produces
      * the document. (Some renderers can store this in the document.)
@@ -580,6 +581,14 @@ public class FOUserAgent {
         return this.eventBroadcaster;
     }
 
+    public boolean isPdfUAEnabled() {
+        return pdfUAEnabled;
+    }
+
+    public void setPdfUAEnabled(boolean pdfUAEnabled) {
+        this.pdfUAEnabled = pdfUAEnabled;
+    }
+
     private class FOPEventBroadcaster extends DefaultEventBroadcaster {
 
         private EventListener rootListener;
index 9a490c58b8db9d564a80a89b3a5688e80b6e02a1..3b59659e76a249ea2319de8561555c436045a609 100644 (file)
@@ -50,6 +50,7 @@ public class BasicLink extends InlineLevel implements StructureTreeElementHolder
     // private ToBeImplementedProperty indicateDestination;
     private String internalDestination;
     private int showDestination;
+    private String altText;
     // private ToBeImplementedProperty targetProcessingContext;
     // private ToBeImplementedProperty targetPresentationContext;
     // private ToBeImplementedProperty targetStylesheet;
@@ -93,6 +94,12 @@ public class BasicLink extends InlineLevel implements StructureTreeElementHolder
             // slightly stronger than spec "should be specified"
             getFOValidationEventProducer().missingLinkDestination(this, getName(), locator);
         }
+        if (getUserAgent().isAccessibilityEnabled()) {
+            altText = pList.get(PR_X_ALT_TEXT).getString();
+            if (altText.equals("") && getUserAgent().isPdfUAEnabled()) {
+                getFOValidationEventProducer().altTextMissing(this, getLocalName(), getLocator());
+            }
+        }
     }
 
     /** {@inheritDoc} */
@@ -212,4 +219,8 @@ public class BasicLink extends InlineLevel implements StructureTreeElementHolder
     public int getNameId() {
         return FO_BASIC_LINK;
     }
+
+    public String getAltText() {
+        return altText;
+    }
 }
index 370c0017a487c36b557c08aa00b2b520addb5932..58b164c8dac24b7f135f9c9e07e59f36f6560cf2 100644 (file)
@@ -38,6 +38,8 @@ import org.apache.xmlgraphics.xmp.schemas.pdf.AdobePDFAdapter;
 import org.apache.xmlgraphics.xmp.schemas.pdf.AdobePDFSchema;
 import org.apache.xmlgraphics.xmp.schemas.pdf.PDFAAdapter;
 import org.apache.xmlgraphics.xmp.schemas.pdf.PDFAXMPSchema;
+import org.apache.xmlgraphics.xmp.schemas.pdf.PDFUAAdapter;
+import org.apache.xmlgraphics.xmp.schemas.pdf.PDFUAXMPSchema;
 import org.apache.xmlgraphics.xmp.schemas.pdf.PDFVTAdapter;
 import org.apache.xmlgraphics.xmp.schemas.pdf.PDFVTXMPSchema;
 import org.apache.xmlgraphics.xmp.schemas.pdf.PDFXAdapter;
@@ -161,6 +163,12 @@ public class PDFMetadata extends PDFStream {
         //Somewhat redundant but some PDF/A checkers issue a warning without this.
         dc.setFormat("application/pdf");
 
+        PDFUAMode pdfuaMode = pdfDoc.getProfile().getPDFUAMode();
+        if (pdfuaMode.isEnabled()) {
+            PDFUAAdapter pdfua = PDFUAXMPSchema.getAdapter(meta);
+            pdfua.setPart(pdfuaMode.getPart());
+        }
+
         //PDF/A identification
         PDFAMode pdfaMode = pdfDoc.getProfile().getPDFAMode();
         if (pdfaMode.isEnabled()) {
index 219623842069987846df0963fe4f6845f2dfb3d2..18140a59692b620a58fe1898291362da59f046ce 100644 (file)
@@ -37,6 +37,8 @@ public class PDFProfile {
      */
     protected PDFAMode pdfAMode = PDFAMode.DISABLED;
 
+    protected PDFUAMode pdfUAMode = PDFUAMode.DISABLED;
+
     /**
      * Indicates the PDF/X mode currently active. Defaults to "no restrictions", i.e.
      * PDF/X not active.
@@ -82,6 +84,10 @@ public class PDFProfile {
         return this.pdfAMode;
     }
 
+    public PDFUAMode getPDFUAMode() {
+        return this.pdfUAMode;
+    }
+
     /** @return true if any PDF/A mode is active */
     public boolean isPDFAActive() {
         return getPDFAMode() != PDFAMode.DISABLED;
@@ -99,6 +105,14 @@ public class PDFProfile {
         validateProfileCombination();
     }
 
+    public void setPDFUAMode(PDFUAMode mode) {
+        if (mode == null) {
+            mode = PDFUAMode.DISABLED;
+        }
+        this.pdfUAMode = mode;
+        validateProfileCombination();
+    }
+
     /** @return the PDF/X mode */
     public PDFXMode getPDFXMode() {
         return this.pdfXMode;
@@ -150,6 +164,8 @@ public class PDFProfile {
             sb.append(getPDFAMode());
         } else if (isPDFXActive()) {
             sb.append(getPDFXMode());
+        } else if (getPDFUAMode().isEnabled()) {
+            sb.append(getPDFUAMode());
         } else {
             sb.append(super.toString());
         }
@@ -234,25 +250,28 @@ public class PDFProfile {
      * Checks a few things required for tagged PDF.
      */
     public void verifyTaggedPDF() {
-        if (getPDFAMode().isLevelA()) {
+        if (getPDFAMode().isLevelA() || getPDFUAMode().isEnabled()) {
             final String err = "{0} requires the {1} dictionary entry to be set";
+            String mode = getPDFAMode().toString();
+            if (getPDFUAMode().isEnabled()) {
+                mode = getPDFUAMode().toString();
+            }
             PDFDictionary markInfo = getDocument().getRoot().getMarkInfo();
             if (markInfo == null) {
                 throw new PDFConformanceException(format(
-                        "{0} requires that the accessibility option in the configuration file be enabled",
-                        getPDFAMode()));
+                        "{0} requires that the accessibility option in the configuration file be enabled", mode));
             }
             if (!Boolean.TRUE.equals(markInfo.get("Marked"))) {
                 throw new PDFConformanceException(format(err,
-                        new Object[] {getPDFAMode(), "Marked"}));
+                        new Object[] {mode, "Marked"}));
             }
             if (getDocument().getRoot().getStructTreeRoot() == null) {
                 throw new PDFConformanceException(format(err,
-                        new Object[] {getPDFAMode(), "StructTreeRoot"}));
+                        new Object[] {mode, "StructTreeRoot"}));
             }
             if (getDocument().getRoot().getLanguage() == null) {
                 throw new PDFConformanceException(format(err,
-                        new Object[] {getPDFAMode(), "Lang"}));
+                        new Object[] {mode, "Lang"}));
             }
         }
     }
@@ -264,13 +283,16 @@ public class PDFProfile {
 
     /** @return true if all fonts need to be embedded. */
     public boolean isFontEmbeddingRequired() {
-        return isPDFAActive() || isPDFXActive();
+        return isPDFAActive() || isPDFXActive() || getPDFUAMode().isEnabled();
     }
 
     /** Checks if a title may be absent. */
     public void verifyTitleAbsent() {
+        final String err = "{0} requires the title to be set.";
+        if (getPDFUAMode().isEnabled()) {
+            throw new PDFConformanceException(format(err, getPDFUAMode()));
+        }
         if (isPDFXActive()) {
-            final String err = "{0} requires the title to be set.";
             throw new PDFConformanceException(format(err, getPDFXMode()));
         }
     }
index 66e58c5f0cf8401d3618666e39c2a1f293a66a7a..32edc34fcc78e61744345dab6db0476c3f8f096f 100644 (file)
@@ -82,6 +82,11 @@ public class PDFRoot extends PDFDictionary {
 
     /** {@inheritDoc} */
     public int output(OutputStream stream) throws IOException {
+        if (document.getProfile().getPDFUAMode().isEnabled()) {
+            PDFDictionary d = new PDFDictionary();
+            d.put("DisplayDocTitle", true);
+            put("ViewerPreferences", d);
+        }
         getDocument().getProfile().verifyTaggedPDF();
         return super.output(stream);
     }
index f9a182ceb93d744a9d946e19abd95942b4224a05..ac755bd4badf8f48806f584348b9ae0ff1483c2b 100644 (file)
@@ -243,6 +243,14 @@ public class PDFStructElem extends StructureHierarchyMember
         return this.kids;
     }
 
+    public int output(OutputStream stream) throws IOException {
+        if (getDocument().getProfile().getPDFUAMode().isEnabled()
+                && entries.containsKey("Alt") && "".equals(get("Alt"))) {
+            put("Alt", "No alternate text specified");
+        }
+        return super.output(stream);
+    }
+
     /**
      * Class representing a placeholder for a PDF Structure Element.
      */
diff --git a/src/java/org/apache/fop/pdf/PDFUAMode.java b/src/java/org/apache/fop/pdf/PDFUAMode.java
new file mode 100644 (file)
index 0000000..5f17aa3
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+
+package org.apache.fop.pdf;
+
+/** Enum class for PDF/UA modes. */
+public enum PDFUAMode {
+
+    /** PDF/UA disabled. */
+    DISABLED("PDF/UA disabled"),
+    /** PDF/UA-1 enabled. */
+    PDFUA_1(1);
+
+    private final String name;
+
+    private final int part;
+
+    /**
+     * Constructor to add a new named item.
+     * @param name Name of the item.
+     */
+    private PDFUAMode(String name) {
+        this.name = name;
+        this.part = 0;
+    }
+
+    private PDFUAMode(int part) {
+        this.name = "PDF/UA-" + part;
+        this.part = part;
+    }
+
+    /** @return the name of the enum */
+    public String getName() {
+        return this.name;
+    }
+
+    public int getPart() {
+        return part;
+    }
+
+    /**
+     * Returns {@code true} if this enum corresponds to one of the available PDF/A modes.
+     *
+     * @return {@code true} if this is not DISABLED
+     */
+    public boolean isEnabled() {
+        return this != DISABLED;
+    }
+
+    /**
+     * Returns the mode enum object given a String.
+     * @param s the string
+     * @return the PDFAMode enum object (DISABLED will be returned if no match is found)
+     */
+    public static PDFUAMode getValueOf(String s) {
+        for (PDFUAMode mode : values()) {
+            if (mode.name.equalsIgnoreCase(s)) {
+                return mode;
+            }
+        }
+        return DISABLED;
+    }
+
+    /** {@inheritDoc} */
+    public String toString() {
+        return name;
+    }
+
+}
index 998dc3adebdcf42f0e90222fac19f16b99eff86a..6bbb2fff5ae3c19c0ee6385bbbf7469451fa3b2f 100644 (file)
@@ -44,6 +44,9 @@ import org.apache.fop.fonts.FontTriplet;
 import org.apache.fop.fonts.LazyFont;
 import org.apache.fop.fonts.SingleByteFont;
 import org.apache.fop.fonts.Typeface;
+import org.apache.fop.pdf.PDFArray;
+import org.apache.fop.pdf.PDFDictionary;
+import org.apache.fop.pdf.PDFName;
 import org.apache.fop.pdf.PDFNumber;
 import org.apache.fop.pdf.PDFStructElem;
 import org.apache.fop.pdf.PDFTextUtil;
@@ -173,6 +176,7 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> {
                 placeImage(rect, xobject);
             }
         } else {
+            addStructTreeBBox(rect);
             drawImageUsingURI(uri, rect);
             if (!getDocumentHandler().getPDFDocument().isLinearizationEnabled()) {
                 flushPDFDoc();
@@ -180,6 +184,20 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> {
         }
     }
 
+    private void addStructTreeBBox(Rectangle rect) {
+        if (accessEnabled && getDocumentHandler().getPDFDocument().getProfile().getPDFUAMode().isEnabled()) {
+            PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
+            PDFDictionary d = new PDFDictionary();
+            int x = rect.x / 1000;
+            int y = rect.y / 1000;
+            int w = rect.width / 1000;
+            int h = rect.height / 1000;
+            d.put("BBox", new PDFArray(x, y, w, h));
+            d.put("O", new PDFName("Layout"));
+            structElem.put("A", d);
+        }
+    }
+
     @Override
     protected void drawImageUsingURI(String uri, Rectangle rect) {
         ImageManager manager = getUserAgent().getImageManager();
@@ -263,6 +281,7 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> {
         if (accessEnabled) {
             PDFStructElem structElem = (PDFStructElem) getContext().getStructureTreeElement();
             prepareImageMCID(structElem);
+            addStructTreeBBox(rect);
         }
         drawImageUsingDocument(doc, rect);
         if (!getDocumentHandler().getPDFDocument().isLinearizationEnabled()) {
@@ -315,6 +334,9 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> {
         }
         if (rect.width != 0 && rect.height != 0) {
             generator.endTextObject();
+            if (accessEnabled && getUserAgent().isPdfUAEnabled()) {
+                generator.beginMarkedContentSequence(null, 0, null);
+            }
             if (fill != null) {
                 if (fill instanceof Color) {
                     generator.updateColor((Color)fill, true, null);
@@ -336,6 +358,9 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> {
             }*/
             sb.append('\n');
             generator.add(sb.toString());
+            if (accessEnabled && getUserAgent().isPdfUAEnabled()) {
+                generator.endMarkedContentSequence();
+            }
         }
     }
 
@@ -345,23 +370,32 @@ public class PDFPainter extends AbstractIFPainter<PDFDocumentHandler> {
             BorderProps left, BorderProps right, Color innerBackgroundColor) throws IFException {
         if (top != null || bottom != null || left != null || right != null) {
             generator.endTextObject();
+            if (accessEnabled && getUserAgent().isPdfUAEnabled()) {
+                generator.beginMarkedContentSequence(null, 0, null);
+            }
             this.borderPainter.drawBorders(rect, top, bottom, left, right, innerBackgroundColor);
+            if (accessEnabled && getUserAgent().isPdfUAEnabled()) {
+                generator.endMarkedContentSequence();
+            }
         }
     }
 
-
-
-
     /** {@inheritDoc} */
     @Override
     public void drawLine(Point start, Point end, int width, Color color, RuleStyle style)
         throws IFException {
         generator.endTextObject();
+        if (accessEnabled && getUserAgent().isPdfUAEnabled()) {
+            generator.beginMarkedContentSequence(null, 0, null);
+        }
         try {
             this.graphicsPainter.drawLine(start, end, width, color, style);
         } catch (IOException ioe) {
             throw new IFException("Cannot draw line", ioe);
         }
+        if (accessEnabled && getUserAgent().isPdfUAEnabled()) {
+            generator.endMarkedContentSequence();
+        }
     }
 
     private Typeface getTypeface(String fontName) {
index bbfb8a9b382dd93a25fefdbe0fc172f1a1275593..5e744788886bcd6686b852e4cbcdeffa629bd761 100644 (file)
@@ -60,6 +60,7 @@ import static org.apache.fop.render.pdf.PDFRendererOption.LINEARIZATION;
 import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FONTS;
 import static org.apache.fop.render.pdf.PDFRendererOption.OUTPUT_PROFILE;
 import static org.apache.fop.render.pdf.PDFRendererOption.PDF_A_MODE;
+import static org.apache.fop.render.pdf.PDFRendererOption.PDF_UA_MODE;
 import static org.apache.fop.render.pdf.PDFRendererOption.PDF_VT_MODE;
 import static org.apache.fop.render.pdf.PDFRendererOption.PDF_X_MODE;
 import static org.apache.fop.render.pdf.PDFRendererOption.VERSION;
@@ -134,6 +135,7 @@ public final class PDFRendererConfig implements RendererConfig {
             try {
                 buildFilterMapFromConfiguration(cfg);
                 parseAndPut(PDF_A_MODE, cfg);
+                parseAndPut(PDF_UA_MODE, cfg);
                 parseAndPut(PDF_X_MODE, cfg);
                 parseAndPut(PDF_VT_MODE, cfg);
                 configureEncryptionParams(cfg, userAgent, strict);
index 8874f566d19608d6ea3959cdb8fff05dc2cce671..422a5d0ac0de33cdef81cf575138003b60f0d1e4 100644 (file)
@@ -24,6 +24,7 @@ import java.net.URISyntaxException;
 
 import org.apache.fop.apps.io.InternalResourceResolver;
 import org.apache.fop.pdf.PDFAMode;
+import org.apache.fop.pdf.PDFUAMode;
 import org.apache.fop.pdf.PDFVTMode;
 import org.apache.fop.pdf.PDFXMode;
 import org.apache.fop.pdf.Version;
@@ -43,6 +44,12 @@ public enum PDFRendererOption implements RendererConfigOption {
             return PDFAMode.getValueOf(value);
         }
     },
+    PDF_UA_MODE("pdf-ua-mode", PDFUAMode.DISABLED) {
+        @Override
+        PDFUAMode deserialize(String value) {
+            return PDFUAMode.getValueOf(value);
+        }
+    },
     /** Rendering Options key for the PDF/X mode, default: {@link PDFXMode#DISABLED} */
     PDF_X_MODE("pdf-x-mode", PDFXMode.DISABLED) {
         @Override
index e4cf2480b17bdf2d16ea154d1050ff040f9106f8..54d236db8e0e95d56a95ed10e2d15fd4befd05dc 100644 (file)
@@ -26,6 +26,7 @@ import java.util.Map;
 
 import org.apache.fop.pdf.PDFAMode;
 import org.apache.fop.pdf.PDFEncryptionParams;
+import org.apache.fop.pdf.PDFUAMode;
 import org.apache.fop.pdf.PDFVTMode;
 import org.apache.fop.pdf.PDFXMode;
 import org.apache.fop.pdf.Version;
@@ -36,6 +37,7 @@ import static org.apache.fop.render.pdf.PDFRendererOption.LINEARIZATION;
 import static org.apache.fop.render.pdf.PDFRendererOption.MERGE_FONTS;
 import static org.apache.fop.render.pdf.PDFRendererOption.OUTPUT_PROFILE;
 import static org.apache.fop.render.pdf.PDFRendererOption.PDF_A_MODE;
+import static org.apache.fop.render.pdf.PDFRendererOption.PDF_UA_MODE;
 import static org.apache.fop.render.pdf.PDFRendererOption.PDF_VT_MODE;
 import static org.apache.fop.render.pdf.PDFRendererOption.PDF_X_MODE;
 import static org.apache.fop.render.pdf.PDFRendererOption.VERSION;
@@ -105,6 +107,10 @@ public final class PDFRendererOptionsConfig {
         return (PDFAMode) properties.get(PDF_A_MODE);
     }
 
+    public PDFUAMode getPDFUAMode() {
+        return (PDFUAMode) properties.get(PDF_UA_MODE);
+    }
+
     public PDFXMode getPDFXMode() {
         return (PDFXMode) properties.get(PDF_X_MODE);
     }
index df6f26c652eca58667fbf7b499a8bd0b79c44329..18a804b5d943da7018c7e6fcc19633fe2d8e6aee 100644 (file)
@@ -164,6 +164,8 @@ class PDFRenderingUtil {
 
     private void updatePDFProfiles() {
         pdfDoc.getProfile().setPDFAMode(rendererConfig.getPDFAMode());
+        pdfDoc.getProfile().setPDFUAMode(rendererConfig.getPDFUAMode());
+        userAgent.setPdfUAEnabled(pdfDoc.getProfile().getPDFUAMode().isEnabled());
         pdfDoc.getProfile().setPDFXMode(rendererConfig.getPDFXMode());
         pdfDoc.getProfile().setPDFVTMode(rendererConfig.getPDFVTMode());
     }
index b00c60a9ba3197ac124510063b2ecf66c6573ad0..790f9746862bd3fcf71d0285d7805e225c7826ae 100644 (file)
@@ -91,7 +91,7 @@ public class PDFStructureTreeBuilder implements StructureTreeEventHandler {
         addBuilder("list-item-body",            StandardStructureTypes.List.LBODY);
         addBuilder("list-item-label",           StandardStructureTypes.List.LBL);
         // Dynamic Effects: Link and Multi Formatting Objects
-        addBuilder("basic-link",                StandardStructureTypes.InlineLevelStructure.LINK);
+        addBuilder("basic-link",                new LinkBuilder());
         // Out-of-Line Formatting Objects
         addBuilder("float",                     StandardStructureTypes.Grouping.DIV);
         addBuilder("footnote",                  StandardStructureTypes.InlineLevelStructure.NOTE);
@@ -247,6 +247,22 @@ public class PDFStructureTreeBuilder implements StructureTreeEventHandler {
 
     }
 
+    private static class LinkBuilder extends DefaultStructureElementBuilder {
+        LinkBuilder() {
+            super(StandardStructureTypes.InlineLevelStructure.LINK);
+        }
+
+        @Override
+        protected void setAttributes(PDFStructElem structElem, Attributes attributes) {
+            super.setAttributes(structElem, attributes);
+            String altTextNode = attributes.getValue(ExtensionElementMapping.URI, "alt-text");
+            if (altTextNode == null) {
+                altTextNode = "No alternate text specified";
+            }
+            structElem.put("Alt", altTextNode);
+        }
+    }
+
     private static class TableBuilder extends DefaultStructureElementBuilder {
 
         TableBuilder() {
@@ -391,7 +407,8 @@ public class PDFStructureTreeBuilder implements StructureTreeEventHandler {
     }
 
     private boolean isPDFA1Safe(String name) {
-        return !(pdfFactory.getDocument().getProfile().getPDFAMode().isPart1()
+        return !((pdfFactory.getDocument().getProfile().getPDFAMode().isPart1()
+                || pdfFactory.getDocument().getProfile().getPDFUAMode().isEnabled())
                 && (name.equals("table-body")
                 || name.equals("table-header")
                 || name.equals("table-footer")));
index 5a34e9e9a26e90008dbff2580d9f698a36b858f0..b103744ae6c3e2ad5ed46b1ea3d873a25d97570c 100644 (file)
@@ -38,7 +38,7 @@
     </fo:static-content>
     <fo:flow flow-name="xsl-region-body">
       <fo:block id="4">This is a link to the <fo:wrapper id="5" color="blue"><fo:basic-link id="6" 
-            internal-destination="second-start">next page-sequence</fo:basic-link></fo:wrapper> 
+            internal-destination="second-start" fox:alt-text="">next page-sequence</fo:basic-link></fo:wrapper> 
         (which starts on page <fo:page-number-citation id="7" ref-id="second-start"/> and ends on 
         page <fo:page-number-citation-last id="8" ref-id="second-end"/>).</fo:block>
       <fo:block id="9" font-family="sans-serif" font-weight="bold" space-before="1em" 
@@ -46,7 +46,7 @@
       <fo:block id="11">This block of text contains a footnote<fo:footnote id="12"><fo:inline id="13" 
             baseline-shift="super" font-size="70%">1</fo:inline><fo:footnote-body id="14"><fo:block 
               id="15">A footnote with a link to the  <fo:wrapper id="16" color="blue"><fo:basic-link 
-                  id="17" external-destination="http://xmlgraphics.apache.org/fop/">FOP 
+                  id="17" external-destination="http://xmlgraphics.apache.org/fop/" fox:alt-text="">FOP 
                   website</fo:basic-link></fo:wrapper></fo:block></fo:footnote-body></fo:footnote> 
         call.</fo:block>
       <fo:table id="18" space-before="1em" width="100%" table-layout="fixed">
diff --git a/test/java/org/apache/fop/pdf/PDFUATestCase.java b/test/java/org/apache/fop/pdf/PDFUATestCase.java
new file mode 100644 (file)
index 0000000..e6d7404
--- /dev/null
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* $Id$ */
+package org.apache.fop.pdf;
+
+import java.awt.geom.Rectangle2D;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import org.apache.xmlgraphics.util.QName;
+import org.apache.xmlgraphics.xmp.Metadata;
+
+import org.apache.fop.render.pdf.PDFContentGenerator;
+
+public class PDFUATestCase {
+    @Test
+    public void testXMP() throws IOException {
+        PDFDocument doc = new PDFDocument("");
+        doc.getProfile().setPDFUAMode(PDFUAMode.PDFUA_1);
+        Metadata metadata = PDFMetadata.createXMPFromPDFDocument(doc);
+        StringBuilder sb = new StringBuilder();
+        Iterator i = metadata.iterator();
+        while (i.hasNext()) {
+            QName k = (QName) i.next();
+            sb.append(k + ": " + metadata.getProperty(k).getValue() + "\n");
+        }
+        String s = sb.toString();
+        Assert.assertTrue(s, s.contains("pdfuaid:part: 1"));
+    }
+
+    @Test
+    public void testPDF() throws IOException {
+        PDFDocument doc = new PDFDocument("");
+        doc.getRoot().makeTagged();
+        doc.getRoot().setStructTreeRoot(new PDFStructTreeRoot(null));
+        doc.getInfo().setTitle("title");
+        doc.getProfile().setPDFUAMode(PDFUAMode.PDFUA_1);
+        PDFResources resources = new PDFResources(doc);
+        doc.addObject(resources);
+        PDFResourceContext context = new PDFResourceContext(resources);
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        PDFContentGenerator gen = new PDFContentGenerator(doc, out, context);
+        Rectangle2D.Float f = new Rectangle2D.Float();
+        PDFPage page = new PDFPage(resources, 0, f, f, f, f);
+        doc.addImage(context, new BitmapImage("", 1, 1, new byte[0], null));
+        doc.registerObject(page);
+        gen.flushPDFDoc();
+        doc.outputTrailer(out);
+
+        Collection<StringBuilder> objs = PDFLinearizationTestCase.readObjs(
+                new ByteArrayInputStream(out.toByteArray())).values();
+        Assert.assertTrue(getObj(objs, "/Type /Catalog").contains("/ViewerPreferences << /DisplayDocTitle true >>"));
+    }
+
+    private String getObj(Collection<StringBuilder> objs, String x) {
+        for (StringBuilder s : objs) {
+            if (s.toString().contains(x)) {
+                return s.toString();
+            }
+        }
+        return null;
+    }
+}