1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements.  See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License.  You may obtain a copy of the License at
8  *
9  *      http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 
18 /* $Id: PDFProfile.java 1753357 2016-07-19 09:25:55Z ssteiner $ */
19 
20 package org.apache.fop.pdf;
21 
22 import java.text.MessageFormat;
23 
24 /**
25  * This class allows tracks the enabled PDF profiles (PDF/A and PDF/X) and provides methods to
26  * the libarary and its users to enable the generation of PDFs conforming to the enabled PDF
27  * profiles.
28  * <p>
29  * Some profile from PDF/X and PDF/A can be active simultaneously (example: PDF/A-1 and
30  * PDF/X-3:2003).
31  */
32 public class PDFProfile {
33 
34     /**
35      * Indicates the PDF/A mode currently active. Defaults to "no restrictions", i.e.
36      * PDF/A not active.
37      */
38     protected PDFAMode pdfAMode = PDFAMode.DISABLED;
39 
40     protected PDFUAMode pdfUAMode = PDFUAMode.DISABLED;
41 
42     /**
43      * Indicates the PDF/X mode currently active. Defaults to "no restrictions", i.e.
44      * PDF/X not active.
45      */
46     protected PDFXMode pdfXMode = PDFXMode.DISABLED;
47 
48     protected PDFVTMode pdfVTMode = PDFVTMode.DISABLED;
49 
50     private PDFDocument doc;
51 
52     /**
53      * Main constructor
54      * @param doc the PDF document
55      */
PDFProfile(PDFDocument doc)56     public PDFProfile(PDFDocument doc) {
57         this.doc = doc;
58     }
59 
60     /**
61      * Validates if the requested profile combination is compatible.
62      */
validateProfileCombination()63     protected void validateProfileCombination() {
64         if (pdfAMode != PDFAMode.DISABLED) {
65             if (pdfAMode == PDFAMode.PDFA_1B) {
66                 if (pdfXMode != PDFXMode.DISABLED && pdfXMode != PDFXMode.PDFX_3_2003 && pdfXMode != PDFXMode.PDFX_4) {
67                     throw new PDFConformanceException(
68                             pdfAMode + " and " + pdfXMode + " are not compatible!");
69                 }
70             }
71         }
72         if (pdfVTMode != PDFVTMode.DISABLED && pdfXMode != PDFXMode.PDFX_4) {
73             throw new PDFConformanceException(pdfVTMode.name() + " requires " + PDFXMode.PDFX_4.getName() + " enabled");
74         }
75     }
76 
77     /** @return the PDFDocument this profile is attached to */
getDocument()78     public PDFDocument getDocument() {
79         return this.doc;
80     }
81 
82     /** @return the PDF/A mode */
getPDFAMode()83     public PDFAMode getPDFAMode() {
84         return this.pdfAMode;
85     }
86 
getPDFUAMode()87     public PDFUAMode getPDFUAMode() {
88         return this.pdfUAMode;
89     }
90 
91     /** @return true if any PDF/A mode is active */
isPDFAActive()92     public boolean isPDFAActive() {
93         return getPDFAMode() != PDFAMode.DISABLED;
94     }
95 
96     /**
97      * Sets the PDF/A mode
98      * @param mode the PDF/A mode
99      */
setPDFAMode(PDFAMode mode)100     public void setPDFAMode(PDFAMode mode) {
101         if (mode == null) {
102             mode = PDFAMode.DISABLED;
103         }
104         this.pdfAMode = mode;
105         validateProfileCombination();
106     }
107 
setPDFUAMode(PDFUAMode mode)108     public void setPDFUAMode(PDFUAMode mode) {
109         if (mode == null) {
110             mode = PDFUAMode.DISABLED;
111         }
112         this.pdfUAMode = mode;
113         validateProfileCombination();
114     }
115 
116     /** @return the PDF/X mode */
getPDFXMode()117     public PDFXMode getPDFXMode() {
118         return this.pdfXMode;
119     }
120 
getPDFVTMode()121     public PDFVTMode getPDFVTMode() {
122         return this.pdfVTMode;
123     }
124 
125     /** @return true if any PDF/X mode is active */
isPDFXActive()126     public boolean isPDFXActive() {
127         return getPDFXMode() != PDFXMode.DISABLED;
128     }
129 
isPDFVTActive()130     public boolean isPDFVTActive() {
131         return getPDFVTMode() != PDFVTMode.DISABLED;
132     }
133 
134     /**
135      * Sets the PDF/X mode
136      * @param mode the PDF/X mode
137      */
setPDFXMode(PDFXMode mode)138     public void setPDFXMode(PDFXMode mode) {
139         if (mode == null) {
140             mode = PDFXMode.DISABLED;
141         }
142         this.pdfXMode = mode;
143         validateProfileCombination();
144     }
145 
146     /**
147      * Sets the PDF/X mode
148      * @param mode the PDF/X mode
149      */
setPDFVTMode(PDFVTMode mode)150     public void setPDFVTMode(PDFVTMode mode) {
151         if (mode == null) {
152             mode = PDFVTMode.DISABLED;
153         }
154         this.pdfVTMode = mode;
155         validateProfileCombination();
156     }
157 
158     /** {@inheritDoc} */
toString()159     public String toString() {
160         StringBuffer sb = new StringBuffer();
161         if (isPDFAActive() && isPDFXActive()) {
162             sb.append("[").append(getPDFAMode()).append(",").append(getPDFXMode()).append("]");
163         } else if (isPDFAActive()) {
164             sb.append(getPDFAMode());
165         } else if (isPDFXActive()) {
166             sb.append(getPDFXMode());
167         } else if (getPDFUAMode().isEnabled()) {
168             sb.append(getPDFUAMode());
169         } else {
170             sb.append(super.toString());
171         }
172         return sb.toString();
173     }
174 
175     //---------=== Info and validation methods ===---------
176 
format(String pattern, Object[] args)177     private String format(String pattern, Object[] args) {
178         return MessageFormat.format(pattern, args);
179     }
180 
format(String pattern, Object arg)181     private String format(String pattern, Object arg) {
182         return format(pattern, new Object[] {arg});
183     }
184 
185     /** Checks if encryption is allowed. */
verifyEncryptionAllowed()186     public void verifyEncryptionAllowed() {
187         final String err = "{0} doesn't allow encrypted PDFs";
188         if (isPDFAActive()) {
189             throw new PDFConformanceException(format(err, getPDFAMode()));
190         }
191         if (isPDFXActive()) {
192             throw new PDFConformanceException(format(err, getPDFXMode()));
193         }
194     }
195 
196     /** Checks if PostScript XObjects are allowed. */
verifyPSXObjectsAllowed()197     public void verifyPSXObjectsAllowed() {
198         final String err = "PostScript XObjects are prohibited when {0}"
199                 + " is active. Convert EPS graphics to another format.";
200         if (isPDFAActive()) {
201             throw new PDFConformanceException(format(err, getPDFAMode()));
202         }
203         if (isPDFXActive()) {
204             throw new PDFConformanceException(format(err, getPDFXMode()));
205         }
206     }
207 
208     /**
209      * Checks if the use of transparency is allowed.
210      * @param context Context information for the user to identify the problem spot
211      */
verifyTransparencyAllowed(String context)212     public void verifyTransparencyAllowed(String context) {
213         Object profile = isTransparencyAllowed();
214         if (profile != null) {
215             throw new TransparencyDisallowedException(profile, context);
216         }
217     }
218 
219     /**
220      * Returns {@code null} if transparency is allowed, otherwise returns the profile that
221      * prevents it.
222      *
223      * @return {@code null}, or an object whose {@code toString} method returns the name
224      * of the profile that disallows transparency
225      */
isTransparencyAllowed()226     public Object isTransparencyAllowed() {
227         if (pdfAMode.isPart1()) {
228             return getPDFAMode();
229         }
230         if (getPDFXMode() == PDFXMode.PDFX_3_2003) {
231             return getPDFXMode();
232         }
233         return null;
234     }
235 
236     /** Checks if the right PDF version is set. */
verifyPDFVersion()237     public void verifyPDFVersion() {
238         String err = "PDF version must be 1.4 for {0}";
239         if (getPDFAMode().isPart1()
240                 && !Version.V1_4.equals(getDocument().getPDFVersion())) {
241             throw new PDFConformanceException(format(err, getPDFAMode()));
242         }
243         if (getPDFXMode() == PDFXMode.PDFX_3_2003
244                 && !Version.V1_4.equals(getDocument().getPDFVersion())) {
245             throw new PDFConformanceException(format(err, getPDFXMode()));
246         }
247     }
248 
249     /**
250      * Checks a few things required for tagged PDF.
251      */
verifyTaggedPDF()252     public void verifyTaggedPDF() {
253         if (getPDFAMode().isLevelA() || getPDFUAMode().isEnabled()) {
254             final String err = "{0} requires the {1} dictionary entry to be set";
255             String mode = getPDFAMode().toString();
256             if (getPDFUAMode().isEnabled()) {
257                 mode = getPDFUAMode().toString();
258             }
259             PDFDictionary markInfo = getDocument().getRoot().getMarkInfo();
260             if (markInfo == null) {
261                 throw new PDFConformanceException(format(
262                         "{0} requires that the accessibility option in the configuration file be enabled", mode));
263             }
264             if (!Boolean.TRUE.equals(markInfo.get("Marked"))) {
265                 throw new PDFConformanceException(format(err,
266                         new Object[] {mode, "Marked"}));
267             }
268             if (getDocument().getRoot().getStructTreeRoot() == null) {
269                 throw new PDFConformanceException(format(err,
270                         new Object[] {mode, "StructTreeRoot"}));
271             }
272             if (getDocument().getRoot().getLanguage() == null) {
273                 throw new PDFConformanceException(format(err,
274                         new Object[] {mode, "Lang"}));
275             }
276         }
277     }
278 
279     /** @return true if the ID entry must be present in the trailer. */
isIDEntryRequired()280     public boolean isIDEntryRequired() {
281         return isPDFAActive() || isPDFXActive();
282     }
283 
284     /** @return true if all fonts need to be embedded. */
isFontEmbeddingRequired()285     public boolean isFontEmbeddingRequired() {
286         return isPDFAActive() || isPDFXActive() || getPDFUAMode().isEnabled();
287     }
288 
289     /** Checks if a title may be absent. */
verifyTitleAbsent()290     public void verifyTitleAbsent() {
291         final String err = "{0} requires the title to be set.";
292         if (getPDFUAMode().isEnabled()) {
293             throw new PDFConformanceException(format(err, getPDFUAMode()));
294         }
295         if (isPDFXActive()) {
296             throw new PDFConformanceException(format(err, getPDFXMode()));
297         }
298     }
299 
300     /** @return true if the ModDate Info entry must be present. */
isModDateRequired()301     public boolean isModDateRequired() {
302         return getPDFXMode() != PDFXMode.DISABLED;
303     }
304 
305     /** @return true if the Trapped Info entry must be present. */
isTrappedEntryRequired()306     public boolean isTrappedEntryRequired() {
307         return getPDFXMode() != PDFXMode.DISABLED;
308     }
309 
310     /** @return true if annotations are allowed */
isAnnotationAllowed()311     public boolean isAnnotationAllowed() {
312         return !isPDFXActive();
313     }
314 
315     /** Checks if annotations are allowed. */
verifyAnnotAllowed()316     public void verifyAnnotAllowed() {
317         if (!isAnnotationAllowed()) {
318             final String err = "{0} does not allow annotations inside the printable area.";
319             //Note: this rule is simplified. Refer to the standard for details.
320             throw new PDFConformanceException(format(err, getPDFXMode()));
321         }
322     }
323 
324     /** Checks if Actions are allowed. */
verifyActionAllowed()325     public void verifyActionAllowed() {
326         if (isPDFXActive()) {
327             final String err = "{0} does not allow Actions.";
328             throw new PDFConformanceException(format(err, getPDFXMode()));
329         }
330     }
331 
332     /** Checks if embedded files are allowed. */
verifyEmbeddedFilesAllowed()333     public void verifyEmbeddedFilesAllowed() {
334         final String err = "{0} does not allow embedded files.";
335         if (isPDFAActive() && getPDFAMode().getPart() < 3) {
336             throw new PDFConformanceException(format(err, getPDFAMode()));
337         }
338         if (isPDFXActive()) {
339             //Implicit since file specs are forbidden
340             throw new PDFConformanceException(format(err, getPDFXMode()));
341         }
342     }
343 
344 }
345