]> source.dussan.org Git - xmlgraphics-fop.git/commitdiff
Bugzilla #51385: Added configuration option to set the version of the output PDF...
authorVincent Hennebert <vhennebert@apache.org>
Mon, 19 Mar 2012 15:56:19 +0000 (15:56 +0000)
committerVincent Hennebert <vhennebert@apache.org>
Mon, 19 Mar 2012 15:56:19 +0000 (15:56 +0000)
Patch by Mehdi Houshmand, applied with minor modifications

git-svn-id: https://svn.apache.org/repos/asf/xmlgraphics/fop/trunk@1302518 13f79535-47bb-0310-9956-ffa450edef68

15 files changed:
src/documentation/content/xdocs/trunk/configuration.xml
src/java/org/apache/fop/pdf/PDFDocument.java
src/java/org/apache/fop/pdf/PDFEmbeddedFile.java
src/java/org/apache/fop/pdf/PDFInfo.java
src/java/org/apache/fop/pdf/PDFObject.java
src/java/org/apache/fop/pdf/PDFProfile.java
src/java/org/apache/fop/pdf/PDFRoot.java
src/java/org/apache/fop/pdf/Version.java [new file with mode: 0644]
src/java/org/apache/fop/pdf/VersionController.java [new file with mode: 0644]
src/java/org/apache/fop/render/pdf/PDFConfigurationConstants.java
src/java/org/apache/fop/render/pdf/PDFRendererConfigurator.java
src/java/org/apache/fop/render/pdf/PDFRenderingUtil.java
status.xml
test/java/org/apache/fop/pdf/VersionControllerTestCase.java [new file with mode: 0644]
test/java/org/apache/fop/pdf/VersionTestCase.java [new file with mode: 0644]

index cd1e1dfc336b3f8cf648767d71adb5fa7027cdea..10769c5ff294f750558854d06593c3311f0efb39 100644 (file)
          <noprinthq/>
       </encryption-params>
     </renderer>]]></source>
+     <p>
+       By default FOP produces PDF files of version 1.4, but this can be changed in order to benefit 
+       from features that appeared in newer versions of PDF. At the moment, only a few features from 
+       PDF 1.5 have been implemented, but the configuration element will accept any value between 
+       1.4 and 1.7. This is the value that will appear in the PDF header, although only features up 
+       to 1.5 will actually be used.
+      <source><![CDATA[
+      <renderer mime="application/pdf">
+        <version>1.5</version>
+        <!-- Versions 1.4, 1.5, 1.6 and 1.7 are accepted, all other values are invalid -->
+      </renderer>]]></source>
+     </p>
       
     </section>
     <section id="ps-renderer">
@@ -522,4 +534,4 @@ information it finds. Check if FOP finds what you expect.</li>
 
         </section>
   </body>
-</document>
\ No newline at end of file
+</document>
index e9206a1bb5838770192ee8f2cbad9b1ff38a6ce5..e9886fc372c477a412042aac6cfe9b5303676e5e 100644 (file)
@@ -65,12 +65,6 @@ public class PDFDocument {
 
     private static final Long LOCATION_PLACEHOLDER = new Long(0);
 
-    /** Integer constant to represent PDF 1.3 */
-    public static final int PDF_VERSION_1_3 = 3;
-
-    /** Integer constant to represent PDF 1.4 */
-    public static final int PDF_VERSION_1_4 = 4;
-
     /** the encoding to use when converting strings to PDF commands */
     public static final String ENCODING = "ISO-8859-1";
 
@@ -95,8 +89,8 @@ public class PDFDocument {
     /** the objects themselves */
     private List objects = new LinkedList();
 
-    /** Indicates what PDF version is active */
-    private int pdfVersion = PDF_VERSION_1_4;
+    /** Controls the PDF version of this document */
+    private VersionController versionController;
 
     /** Indicates which PDF profiles are active (PDF/A, PDF/X etc.) */
     private PDFProfile pdfProfile = new PDFProfile(this);
@@ -197,6 +191,24 @@ public class PDFDocument {
      * @param prod the name of the producer of this pdf document
      */
     public PDFDocument(String prod) {
+        this(prod, null);
+        versionController = VersionController.getDynamicVersionController(Version.V1_4, this);
+    }
+
+    /**
+     * Creates an empty PDF document.
+     *
+     * The constructor creates a /Root and /Pages object to
+     * track the document but does not write these objects until
+     * the trailer is written. Note that the object ID of the
+     * pages object is determined now, and the xref table is
+     * updated later. This allows Pages to refer to their
+     * Parent before we write it out.
+     *
+     * @param prod the name of the producer of this pdf document
+     * @param versionController the version controller of this PDF document
+     */
+    public PDFDocument(String prod, VersionController versionController) {
 
         this.factory = new PDFFactory(this);
 
@@ -211,26 +223,32 @@ public class PDFDocument {
 
         // Make the /Info record
         this.info = getFactory().makeInfo(prod);
+
+        this.versionController = versionController;
+    }
+
+    /**
+     * Returns the current PDF version.
+     *
+     * @return returns the PDF version
+     */
+    public Version getPDFVersion() {
+        return versionController.getPDFVersion();
     }
 
     /**
-     * @return the integer representing the active PDF version
-     *          (one of PDFDocument.PDF_VERSION_*)
+     * Sets the PDF version of this document.
+     *
+     * @param version the PDF version
+     * @throws IllegalStateException if the version of this PDF is not allowed to change.
      */
-    public int getPDFVersion() {
-        return this.pdfVersion;
+    public void setPDFVersion(Version version) {
+        versionController.setPDFVersion(version);
     }
 
-    /** @return the String representing the active PDF version */
+    /** @return the String representing the current PDF version */
     public String getPDFVersionString() {
-        switch (getPDFVersion()) {
-        case PDF_VERSION_1_3:
-            return "1.3";
-        case PDF_VERSION_1_4:
-            return "1.4";
-        default:
-            throw new IllegalStateException("Unsupported PDF version selected");
-        }
+        return versionController.getPDFVersion().toString();
     }
 
     /** @return the PDF profile currently active. */
index a5b44710a5529f0e7a3176956c99cf4e8f1fca42..dc0681405b8a9995cf8855618609c6e5657755c6 100644 (file)
@@ -34,7 +34,7 @@ public class PDFEmbeddedFile extends PDFStream {
         super();
         put("Type", new PDFName("EmbeddedFile"));
         PDFDictionary params = new PDFDictionary();
-        params.put("CreationDate", params.formatDateTime(new Date()));
+        params.put("CreationDate", PDFInfo.formatDateTime(new Date()));
         put("Params", params);
     }
 
index dabd886852ed8c5030596a551321232fb7657e04..3f3fb3b46d981cf64f435900fc97ff70a5ed209b 100644 (file)
@@ -21,7 +21,11 @@ package org.apache.fop.pdf;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
 import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
 
 /**
  * class representing an /Info object
@@ -232,5 +236,67 @@ public class PDFInfo extends PDFObject {
         return bout.toByteArray();
     }
 
+    /**
+     * Returns a SimpleDateFormat instance for formatting PDF date-times.
+     * @return a new SimpleDateFormat instance
+     */
+    protected static SimpleDateFormat getPDFDateFormat() {
+        SimpleDateFormat df = new SimpleDateFormat("'D:'yyyyMMddHHmmss", Locale.ENGLISH);
+        df.setTimeZone(TimeZone.getTimeZone("GMT"));
+        return df;
+    }
+
+    /**
+     * Formats a date/time according to the PDF specification (D:YYYYMMDDHHmmSSOHH'mm').
+     * @param time date/time value to format
+     * @param tz the time zone
+     * @return the requested String representation
+     */
+    protected static String formatDateTime(Date time, TimeZone tz) {
+        Calendar cal = Calendar.getInstance(tz, Locale.ENGLISH);
+        cal.setTime(time);
+
+        int offset = cal.get(Calendar.ZONE_OFFSET);
+        offset += cal.get(Calendar.DST_OFFSET);
+
+        // DateFormat is operating on GMT so adjust for time zone offset
+        Date dt1 = new Date(time.getTime() + offset);
+        StringBuffer sb = new StringBuffer();
+        sb.append(getPDFDateFormat().format(dt1));
+
+        offset /= (1000 * 60); // Convert to minutes
+
+        if (offset == 0) {
+            sb.append('Z');
+        } else {
+            if (offset > 0) {
+                sb.append('+');
+            } else {
+                sb.append('-');
+            }
+            int offsetHour = Math.abs(offset / 60);
+            int offsetMinutes = Math.abs(offset % 60);
+            if (offsetHour < 10) {
+                sb.append('0');
+            }
+            sb.append(Integer.toString(offsetHour));
+            sb.append('\'');
+            if (offsetMinutes < 10) {
+                sb.append('0');
+            }
+            sb.append(Integer.toString(offsetMinutes));
+            sb.append('\'');
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Formats a date/time according to the PDF specification. (D:YYYYMMDDHHmmSSOHH'mm').
+     * @param time date/time value to format
+     * @return the requested String representation
+     */
+    protected static String formatDateTime(Date time) {
+        return formatDateTime(time, TimeZone.getDefault());
+    }
 }
 
index d7413322291b07fefceaa1ca3756223b94c32b33..5abb5a142ab5360300f214a6359ba814dac0daf9 100644 (file)
@@ -23,11 +23,6 @@ package org.apache.fop.pdf;
 import java.io.IOException;
 import java.io.OutputStream;
 import java.io.Writer;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -339,71 +334,6 @@ public abstract class PDFObject implements PDFWritable {
         }
     }
 
-    /**
-     * Returns a SimpleDateFormat instance for formatting PDF date-times.
-     * @return a new SimpleDateFormat instance
-     */
-    protected SimpleDateFormat getPDFDateFormat() {
-        SimpleDateFormat df = new SimpleDateFormat("'D:'yyyyMMddHHmmss", Locale.ENGLISH);
-        df.setTimeZone(TimeZone.getTimeZone("GMT"));
-        return df;
-    }
-
-    /**
-     * Formats a date/time according to the PDF specification
-     * (D:YYYYMMDDHHmmSSOHH'mm').
-     * @param time date/time value to format
-     * @param tz the time zone
-     * @return the requested String representation
-     */
-    protected String formatDateTime(Date time, TimeZone tz) {
-        Calendar cal = Calendar.getInstance(tz, Locale.ENGLISH);
-        cal.setTime(time);
-
-        int offset = cal.get(Calendar.ZONE_OFFSET);
-        offset += cal.get(Calendar.DST_OFFSET);
-
-        //DateFormat is operating on GMT so adjust for time zone offset
-        Date dt1 = new Date(time.getTime() + offset);
-        StringBuffer sb = new StringBuffer();
-        sb.append(getPDFDateFormat().format(dt1));
-
-        offset /= (1000 * 60); //Convert to minutes
-
-        if (offset == 0) {
-            sb.append('Z');
-        } else {
-            if (offset > 0) {
-                sb.append('+');
-            } else {
-                sb.append('-');
-            }
-            int offsetHour = Math.abs(offset / 60);
-            int offsetMinutes = Math.abs(offset % 60);
-            if (offsetHour < 10) {
-                sb.append('0');
-            }
-            sb.append(Integer.toString(offsetHour));
-            sb.append('\'');
-            if (offsetMinutes < 10) {
-                sb.append('0');
-            }
-            sb.append(Integer.toString(offsetMinutes));
-            sb.append('\'');
-        }
-        return sb.toString();
-    }
-
-    /**
-     * Formats a date/time according to the PDF specification.
-     * (D:YYYYMMDDHHmmSSOHH'mm').
-     * @param time date/time value to format
-     * @return the requested String representation
-     */
-    protected String formatDateTime(Date time) {
-        return formatDateTime(time, TimeZone.getDefault());
-    }
-
     /**
      * Check if the other PDFObject has the same content as the current object.
      * <p>
index b4b43c4220bc55997c0e597cf8d59cec49a7a33c..580a9a791c195333f092b8bb8d1e3e42898c187e 100644 (file)
@@ -184,11 +184,11 @@ public class PDFProfile {
     public void verifyPDFVersion() {
         final String err = "PDF version must be 1.4 for {0}";
         if (getPDFAMode().isPDFA1LevelB()
-                && getDocument().getPDFVersion() != PDFDocument.PDF_VERSION_1_4) {
+                && !Version.V1_4.equals(getDocument().getPDFVersion())) {
             throw new PDFConformanceException(format(err, getPDFAMode()));
         }
         if (getPDFXMode() == PDFXMode.PDFX_3_2003
-                && getDocument().getPDFVersion() != PDFDocument.PDF_VERSION_1_4) {
+                && !Version.V1_4.equals(getDocument().getPDFVersion())) {
             throw new PDFConformanceException(format(err, getPDFXMode()));
         }
     }
index 76f2587def77483e82ad5c8d74340e8424b58ed8..81b93b159f767b29f17a62202d8dae1e79f0c019 100644 (file)
@@ -205,7 +205,7 @@ public class PDFRoot extends PDFDictionary {
      * @since PDF 1.4
      */
     public void setMetadata(PDFMetadata meta) {
-        if (getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) {
+        if (getDocumentSafely().getPDFVersion().compareTo(Version.V1_4) >= 0) {
             put("Metadata", meta.makeReference());
         }
     }
@@ -235,7 +235,7 @@ public class PDFRoot extends PDFDictionary {
      * @since PDF 1.4
      */
     public void addOutputIntent(PDFOutputIntent outputIntent) {
-        if (getDocumentSafely().getPDFVersion() >= PDFDocument.PDF_VERSION_1_4) {
+        if (getDocumentSafely().getPDFVersion().compareTo(Version.V1_4) >= 0) {
             PDFArray outputIntents = getOutputIntents();
             if (outputIntents == null) {
                 outputIntents = new PDFArray(this);
@@ -245,6 +245,17 @@ public class PDFRoot extends PDFDictionary {
         }
     }
 
+    /**
+     * Sets the "Version" entry. If this version is greater than that specified in the header, this
+     * version takes precedence.
+     *
+     * @param version the PDF document version
+     * @since PDF 1.4
+     */
+    void setVersion(Version version) {
+        put("Version", new PDFName(version.toString()));
+    }
+
     /**
      * Returns the language identifier of the document.
      * @return the language identifier of the document (or null if not set or undefined)
diff --git a/src/java/org/apache/fop/pdf/Version.java b/src/java/org/apache/fop/pdf/Version.java
new file mode 100644 (file)
index 0000000..0df63d3
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * 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;
+
+/**
+ * A version of PDF. Values are ordered such that compareTo() gives sensible
+ * results (e.g., {@code V1_4.compareTo(V1_5) < 0}).
+ */
+public enum Version {
+    /** PDF v1 */
+    V1_0("1.0"),
+    /** PDF v1.1 */
+    V1_1("1.1"),
+    /** PDF v1.2 */
+    V1_2("1.2"),
+    /** PDF v1.3 */
+    V1_3("1.3"),
+    /** PDF v1.4 */
+    V1_4("1.4"),
+    /** PDF v1.5 */
+    V1_5("1.5"),
+    /** PDF v1.6 */
+    V1_6("1.6"),
+    /** PDF v1.7 */
+    V1_7("1.7");
+
+    private String version;
+
+    private Version(String version) {
+        this.version = version;
+    }
+
+    /**
+     * Given the PDF version as a String, returns the corresponding enumerated type. The String
+     * should be in the format "1.x" for PDF v1.x.
+     *
+     * @param version a version number
+     * @return the corresponding Version instance
+     * @throws IllegalArgumentException if the argument does not correspond to any existing PDF version
+     */
+    public static Version getValueOf(String version) {
+        for (Version pdfVersion : Version.values()) {
+            if (pdfVersion.toString().equals(version)) {
+                return pdfVersion;
+            }
+        }
+        throw new IllegalArgumentException("Invalid PDF version given: " + version);
+    }
+
+    @Override
+    public String toString() {
+        return version;
+    }
+}
diff --git a/src/java/org/apache/fop/pdf/VersionController.java b/src/java/org/apache/fop/pdf/VersionController.java
new file mode 100644 (file)
index 0000000..4a92f49
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * 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;
+
+/**
+ * An abstraction that controls the mutability of the PDF version for a document.
+ */
+public abstract class VersionController {
+
+    private Version version;
+
+    private VersionController(Version version) {
+        this.version = version;
+    }
+
+    /**
+     * Returns the PDF version of the document.
+     *
+     * @return the PDF version
+     */
+    public Version getPDFVersion() {
+        return version;
+    }
+
+    /**
+     * Sets the PDF version of the document.
+     *
+     * @param version the PDF version
+     * @throws IllegalStateException if the PDF version is not allowed to change.
+     */
+    public abstract void setPDFVersion(Version version);
+
+    @Override
+    public String toString() {
+        return version.toString();
+    }
+
+    /**
+     * A class representing the version of a PDF document. This class doesn't allow the version to
+     * change once it has been set, it is immutable. Any attempt to set the version will result in
+     * an exception being thrown.
+     */
+    private static final class FixedVersion extends VersionController {
+
+        private FixedVersion(Version version) {
+            super(version);
+        }
+
+        @Override
+        public void setPDFVersion(Version version) {
+            throw new IllegalStateException("Cannot change the version of this PDF document.");
+        }
+    }
+
+    /**
+     * A class representing the version of a PDF document. This class allows the version to be
+     * changed once it has been set (it is mutable) ONLY if the new version is greater. If the PDF
+     * version is changed after it has been instantiated, the version will be set in the document
+     * catalog.
+     */
+    private static final class DynamicVersion extends VersionController {
+
+        private PDFDocument doc;
+
+        private DynamicVersion(Version version, PDFDocument doc) {
+            super(version);
+            this.doc = doc;
+        }
+
+        @Override
+        public void setPDFVersion(Version version) {
+            if (super.version.compareTo(version) < 0) {
+                super.version = version;
+                doc.getRoot().setVersion(version);
+            }
+        }
+    }
+
+    /**
+     * Returns a controller that disallows subsequent change to the document's version. The minimum
+     * allowed version is v1.4.
+     *
+     * @param version the PDF version (must be &gt;= v1.4)
+     * @return the fixed PDF version controller
+     */
+    public static VersionController getFixedVersionController(Version version) {
+        if (version.compareTo(Version.V1_4) < 0) {
+            throw new IllegalArgumentException("The PDF version cannot be set below version 1.4");
+        }
+        return new FixedVersion(version);
+    }
+
+    /**
+     * Returns a controller that allows subsequent changes to the document's version.
+     *
+     * @param initialVersion the initial PDF version
+     * @param doc the document whose version is being set
+     * @return the dynamic PDF version controller
+     */
+    public static VersionController getDynamicVersionController(Version initialVersion,
+            PDFDocument doc) {
+        return new DynamicVersion(initialVersion, doc);
+    }
+}
index 994cbc0de1cac2152fa8703f59cab2675adc46a5..4a8a03b27023ec3d8a146b8fe6f3f7d867e28e95 100644 (file)
@@ -74,6 +74,8 @@ public interface PDFConfigurationConstants {
     String PDF_X_MODE = "pdf-x-mode";
     /** Rendering Options key for the ICC profile for the output intent. */
     String KEY_OUTPUT_PROFILE = "output-profile";
+    /** PDF version entry: specify the version of the PDF document created, datatype: String */
+    String PDF_VERSION = "version";
     /**
      * Rendering Options key for disabling the sRGB color space (only possible if no PDF/A or
      * PDF/X profile is active).
index dcc7dd32e3bf170c16dd596be81d3c4b36757f10..df5224c047cd30f90895ddb70cb828720edfc403 100644 (file)
@@ -157,6 +157,8 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
             pdfUtil.setDisableSRGBColorSpace(
                     disableColorSpaceConfig.getValueAsBoolean(false));
         }
+
+        setPDFDocVersion(cfg, pdfUtil);
     }
 
     private int checkEncryptionLength(int encryptionLength) {
@@ -175,6 +177,22 @@ public class PDFRendererConfigurator extends PrintRendererConfigurator {
         return correctEncryptionLength;
     }
 
+    private void setPDFDocVersion(Configuration cfg, PDFRenderingUtil pdfUtil) throws FOPException {
+        Configuration pdfVersion = cfg.getChild(PDFConfigurationConstants.PDF_VERSION, false);
+        if (pdfVersion != null) {
+            String version = pdfVersion.getValue(null);
+            if (version != null && version.length() != 0) {
+                try {
+                    pdfUtil.setPDFVersion(version);
+                } catch (IllegalArgumentException e) {
+                    throw new FOPException(e.getMessage());
+                }
+            } else {
+                throw new FOPException("The PDF version has not been set.");
+            }
+        }
+    }
+
     /**
      * Builds a filter map from an Avalon Configuration object.
      *
index c662b0345cfb0c77c80b328796c0ffdee68f18e9..83f6ccab67b1bb3ea188ea7dc5fb280473e293be 100644 (file)
@@ -64,6 +64,8 @@ import org.apache.fop.pdf.PDFPageLabels;
 import org.apache.fop.pdf.PDFReference;
 import org.apache.fop.pdf.PDFText;
 import org.apache.fop.pdf.PDFXMode;
+import org.apache.fop.pdf.Version;
+import org.apache.fop.pdf.VersionController;
 import org.apache.fop.render.pdf.extensions.PDFEmbeddedFileExtensionAttachment;
 
 /**
@@ -102,6 +104,8 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
     /** Optional URI to an output profile to be used. */
     protected String outputProfileURI;
 
+    protected Version maxPDFVersion;
+
 
     PDFRenderingUtil(FOUserAgent userAgent) {
         this.userAgent = userAgent;
@@ -375,8 +379,16 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
         if (this.pdfDoc != null) {
             throw new IllegalStateException("PDFDocument already set up");
         }
-        this.pdfDoc = new PDFDocument(
-                userAgent.getProducer() != null ? userAgent.getProducer() : "");
+
+        String producer = userAgent.getProducer() != null ? userAgent.getProducer() : "";
+
+        if (maxPDFVersion == null) {
+            this.pdfDoc = new PDFDocument(producer);
+        } else {
+            VersionController controller =
+                    VersionController.getFixedVersionController(maxPDFVersion);
+            this.pdfDoc = new PDFDocument(producer, controller);
+        }
         updateInfo();
         updatePDFProfiles();
         pdfDoc.setFilterMap(filterMap);
@@ -482,4 +494,14 @@ class PDFRenderingUtil implements PDFConfigurationConstants {
         nameArray.add(new PDFReference(fileSpec));
     }
 
+    /**
+     * Sets the PDF version of the output document. See {@link Version} for the format of
+     * <code>version</code>.
+     * @param version the PDF version
+     * @throws IllegalArgumentException if the format of version doesn't conform to that specified
+     * by {@link Version}
+     */
+    public void setPDFVersion(String version) {
+        maxPDFVersion = Version.getValueOf(version);
+    }
 }
index 52bf109c436e196cd23962366bad935f6a4f5ecc..e34ecdb4877fb63da3b7e170fd77c5700999eadb 100644 (file)
@@ -62,6 +62,9 @@
       documents. Example: the fix of marks layering will be such a case when it's done.
     -->
     <release version="FOP Trunk" date="TBD">
+      <action context="Code" dev="VH" type="add" fixes-bug="51385" due-to="Mehdi Houshmand">
+        Added configuration option to set the version of the output PDF document.
+      </action>
       <action context="Code" dev="MH" type="fix" fixes-bug="52849" due-to="Luis Bernardo">
         Fixed bug that caused a configured and installed SVG font to stroked,
         also added an event indicating when fonts are stroked.
diff --git a/test/java/org/apache/fop/pdf/VersionControllerTestCase.java b/test/java/org/apache/fop/pdf/VersionControllerTestCase.java
new file mode 100644 (file)
index 0000000..74637c9
--- /dev/null
@@ -0,0 +1,143 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+/**
+ * A test class for {@link VersionController}.
+ */
+public class VersionControllerTestCase {
+
+    private PDFDocument doc;
+
+    @Before
+    public void setUp() {
+        doc = new PDFDocument("test");
+    }
+
+    @Test
+    public void testGetVersion() {
+        // These do the same thing
+        for (Version version : Version.values()) {
+            if (version.compareTo(Version.V1_4) >= 0) {
+                VersionController fixedVC = VersionController.getFixedVersionController(version);
+                assertEquals(version, fixedVC.getPDFVersion());
+            }
+
+            VersionController dynamicVC = VersionController.getDynamicVersionController(version,
+                    doc);
+            assertEquals(version, dynamicVC.getPDFVersion());
+        }
+    }
+
+    /**
+     * Tests that the setter methods work at setting the underlying version.
+     * Here there is a disparity between the two objects, the fixed version will
+     * throw an exception if the setter is invoked. The dynamic version will
+     * allow the version to be changed, if the new version is greater than the
+     * version already set.
+     */
+    @Test
+    public void testSetVersion() {
+        // Create every type of expected PDFVersion
+        for (Version originalVersion : Version.values()) {
+            // Compare against every type of Version
+            for (Version setVersion : Version.values()) {
+                testDynamicController(originalVersion, setVersion);
+                testFixedController(originalVersion, setVersion);
+            }
+
+        }
+    }
+
+    /**
+     * The fixed implementation will throw an exception if an attempt is made to change its
+     * version.
+     *
+     * @param originalVersion the version given to the constructor when PDFVersion instantiated
+     * @param setVersion the version being set
+     */
+    private void testFixedController(Version originalVersion, Version setVersion) {
+        if (originalVersion.compareTo(Version.V1_4) >= 0) {
+            VersionController fixedVC = VersionController
+                    .getFixedVersionController(originalVersion);
+            try {
+                fixedVC.setPDFVersion(setVersion);
+                fail("The FixedVersionController should throw an exception if an attempt to change "
+                        + "the version is made");
+            } catch (IllegalStateException e) {
+                // PASS
+            }
+            // Changes are NOT allowed, the original version is immutable
+            assertEquals(originalVersion, fixedVC.getPDFVersion());
+            // The document version is NEVER changed
+            assertEquals(Version.V1_4, doc.getPDFVersion());
+            // the /Version parameter shouldn't be present in the document catalog
+            assertNull(doc.getRoot().get("Version"));
+        } else {
+            try {
+                VersionController.getFixedVersionController(originalVersion);
+                fail("Versions < 1.4 aren't allowed.");
+            } catch (IllegalArgumentException e) {
+                // PASS
+            }
+        }
+    }
+
+    /**
+     * The dynamic implementation allows the version to be changed. However, the version given in
+     * the constructor will be the version set in the header of the PDF document. Any change to the
+     * version will then be made in the document catalog.
+     *
+     * @param originalVersion the version given to the constructor when PDFVersion instantiated
+     * @param setVersion the version being set
+     */
+    private void testDynamicController(Version originalVersion, Version setVersion) {
+        VersionController testSubj = VersionController.getDynamicVersionController(originalVersion,
+                doc);
+        testSubj.setPDFVersion(setVersion);
+        PDFName nameVersion = new PDFName(setVersion.toString());
+
+        if (originalVersion.compareTo(setVersion) < 0) {
+            versionShouldChange(setVersion, testSubj, nameVersion);
+        } else {
+            versionShouldNotChange(originalVersion, testSubj);
+        }
+        doc.getRoot().put("Version", null);
+    }
+
+    private void versionShouldNotChange(Version originalVersion, VersionController testSubj) {
+        assertEquals(originalVersion, testSubj.getPDFVersion());
+        assertNull(doc.getRoot().get("Version"));
+    }
+
+    private void versionShouldChange(Version setVersion, VersionController testSubj,
+            PDFName nameVersion) {
+        assertEquals(setVersion, testSubj.getPDFVersion());
+        assertEquals(nameVersion.toString(), doc.getRoot().get("Version").toString());
+    }
+}
diff --git a/test/java/org/apache/fop/pdf/VersionTestCase.java b/test/java/org/apache/fop/pdf/VersionTestCase.java
new file mode 100644 (file)
index 0000000..9c90f09
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+/**
+ * This is a test case for ({@link Version}.
+ */
+public class VersionTestCase {
+
+    /**
+     * Test the <code>getValue()</code> method. This should return {@link Version} given a
+     * {@link String}.
+     */
+    @Test
+    public void testGetValue() {
+        int index = 0;
+        for (Version version : Version.values()) {
+            assertEquals(version, Version.getValueOf("1." + index++));
+        }
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testGetValueIllegalArgument() {
+        Version.getValueOf("blah");
+    }
+
+    /**
+     * Tests that the <code>toString()</method> method returns the PDF version string of the proper
+     * format.
+     */
+    @Test
+    public void testToString() {
+        // Test all the normal values
+        int index = 0;
+        for (Version version : Version.values()) {
+            assertTrue(version.toString().equals("1." + index++));
+        }
+    }
+
+    /**
+     * Tests that the <code>compareTo()</code> contract is obeyed.
+     */
+    @Test
+    public void testCompareTo() {
+        // Ensure that the implicit comparison contract is satisfied
+        Version[] expected = {
+                Version.V1_0,
+                Version.V1_1,
+                Version.V1_2,
+                Version.V1_3,
+                Version.V1_4,
+                Version.V1_5,
+                Version.V1_6,
+                Version.V1_7
+        };
+
+        Version[] actual = Version.values();
+
+        for (int i = 0; i < actual.length - 1; i++) {
+            assertEquals(-1, actual[i].compareTo(expected[i + 1]));
+
+            assertEquals(0, actual[i].compareTo(expected[i]));
+
+            assertEquals(1, actual[i + 1].compareTo(expected[i]));
+        }
+    }
+}