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: PDFRoot.java 1863870 2019-07-27 13:23:59Z matthias $ */
19 
20 package org.apache.fop.pdf;
21 
22 import java.io.IOException;
23 import java.io.OutputStream;
24 import java.util.Locale;
25 
26 import org.apache.fop.util.LanguageTags;
27 
28 /**
29  * Class representing a Root (/Catalog) object.
30  */
31 public class PDFRoot extends PDFDictionary {
32 
33     /**
34      * Use no page mode setting, default
35      */
36     public static final int PAGEMODE_USENONE = 0;
37 
38     /**
39      * Use outlines page mode to show bookmarks
40      */
41     public static final int PAGEMODE_USEOUTLINES = 1;
42 
43     /**
44      * Use thumbs page mode to show thumbnail images
45      */
46     public static final int PAGEMODE_USETHUMBS = 2;
47 
48     /**
49      * Full screen page mode
50      */
51     public static final int PAGEMODE_FULLSCREEN = 3;
52 
53     private final PDFDocument document;
54 
55     private PDFDPartRoot dPartRoot;
56     private PDFArray af;
57 
58     private static final PDFName[] PAGEMODE_NAMES = new PDFName[] {
59         new PDFName("UseNone"),
60         new PDFName("UseOutlines"),
61         new PDFName("UseThumbs"),
62         new PDFName("FullScreen"),
63     };
64 
65     /**
66      * create a Root (/Catalog) object. NOTE: The PDFRoot
67      * object must be created before the PDF document is
68      * generated, but it is not assigned an object ID until
69      * it is about to be written (immediately before the xref
70      * table as part of the trailer). (mark-fop@inomial.com)
71      *
72      * @param document TODO
73      * @param pages the PDFPages object
74      */
PDFRoot(PDFDocument document, PDFPages pages)75     public PDFRoot(PDFDocument document, PDFPages pages) {
76         this.document = document;
77         setObjectNumber(document);
78         put("Type", new PDFName("Catalog"));
79         setRootPages(pages);
80         setLanguage("x-unknown");
81     }
82 
83     /** {@inheritDoc} */
output(OutputStream stream)84     public int output(OutputStream stream) throws IOException {
85         if (document.getProfile().getPDFUAMode().isEnabled()) {
86             PDFDictionary d = new PDFDictionary();
87             d.put("DisplayDocTitle", true);
88             put("ViewerPreferences", d);
89         }
90         getDocument().getProfile().verifyTaggedPDF();
91         return super.output(stream);
92     }
93 
94     /**
95      * Set the page mode for the PDF document.
96      *
97      * @param mode the page mode (one of PAGEMODE_*)
98      */
setPageMode(int mode)99     public void setPageMode(int mode) {
100         put("PageMode", PAGEMODE_NAMES[mode]);
101     }
102 
103     /**
104      * Returns the currently active /PageMode.
105      * @return the /PageMode (one of PAGEMODE_*)
106      */
getPageMode()107     public int getPageMode() {
108         PDFName mode = (PDFName)get("PageMode");
109         if (mode != null) {
110             for (int i = 0; i < PAGEMODE_NAMES.length; i++) {
111                 if (PAGEMODE_NAMES[i].equals(mode)) {
112                     return i;
113                 }
114             }
115             throw new IllegalStateException("Unknown /PageMode encountered: " + mode);
116         } else {
117             return PAGEMODE_USENONE;
118         }
119     }
120 
121     /**
122      * add a /Page object to the root /Pages object
123      *
124      * @param page the /Page object to add
125      */
addPage(PDFPage page)126     public void addPage(PDFPage page) {
127         PDFPages pages = getRootPages();
128         pages.addPage(page);
129     }
130 
131     /**
132      * set the root /Pages object
133      *
134      * @param pages the /Pages object to set as root
135      */
setRootPages(PDFPages pages)136     public void setRootPages(PDFPages pages) {
137         put("Pages", pages.makeReference());
138     }
139 
140     /**
141      * Returns the /PageLabels object.
142      * @return the /PageLabels object if set, null otherwise.
143      * @since PDF 1.3
144      */
getRootPages()145     public PDFPages getRootPages() {
146         PDFReference ref = (PDFReference)get("Pages");
147         return (ref != null ? (PDFPages)ref.getObject() : null);
148     }
149 
150     /**
151      * Sets the /PageLabels object.
152      * @param pageLabels the /PageLabels object
153      */
setPageLabels(PDFPageLabels pageLabels)154     public void setPageLabels(PDFPageLabels pageLabels) {
155         put("PageLabels", pageLabels.makeReference());
156     }
157 
158     /**
159      * Returns the /PageLabels object.
160      * @return the /PageLabels object if set, null otherwise.
161      * @since PDF 1.3
162      */
getPageLabels()163     public PDFPageLabels getPageLabels() {
164         PDFReference ref = (PDFReference)get("PageLabels");
165         return (ref != null ? (PDFPageLabels)ref.getObject() : null);
166     }
167 
168     /**
169      * Set the root outline for the PDF document.
170      *
171      * @param out the root PDF Outline
172      */
setRootOutline(PDFOutline out)173     public void setRootOutline(PDFOutline out) {
174         put("Outlines", out.makeReference());
175 
176         //Set /PageMode to /UseOutlines by default if no other mode has been set
177         PDFName mode = (PDFName)get("PageMode");
178         if (mode == null) {
179             setPageMode(PAGEMODE_USEOUTLINES);
180         }
181     }
182 
183     /**
184      * Get the root PDF outline for the document.
185      *
186      * @return the root PDF Outline
187      */
getRootOutline()188     public PDFOutline getRootOutline() {
189         PDFReference ref = (PDFReference)get("Outlines");
190         return (ref != null ? (PDFOutline)ref.getObject() : null);
191     }
192 
193     /**
194      * Set the /Names object.
195      * @param names the Names object
196      * @since PDF 1.2
197      */
setNames(PDFNames names)198     public void setNames(PDFNames names) {
199         put("Names", names.makeReference());
200     }
201 
202     /**
203      * Returns the /Names object.
204      * @return the Names object if set, null otherwise.
205      * @since PDF 1.2
206      */
getNames()207     public PDFNames getNames() {
208         PDFReference ref = (PDFReference)get("Names");
209         return (ref != null ? (PDFNames)ref.getObject() : null);
210     }
211 
212     /**
213      * Set the optional Metadata object.
214      * @param meta the Metadata object
215      * @since PDF 1.4
216      */
setMetadata(PDFMetadata meta)217     public void setMetadata(PDFMetadata meta) {
218         if (getDocumentSafely().getPDFVersion().compareTo(Version.V1_4) >= 0) {
219             put("Metadata", meta.makeReference());
220         }
221     }
222 
223     /**
224      * Returns the /Metadata object
225      * @return the /Metadata object if set, null otherwise.
226      * @since PDF 1.4
227      */
getMetadata()228     public PDFMetadata getMetadata() {
229         PDFReference ref = (PDFReference)get("Metadata");
230         return (ref != null ? (PDFMetadata)ref.getObject() : null);
231     }
232 
233     /**
234      * Returns the /OutputIntents array.
235      * @return the /OutputIntents array or null if it doesn't exist
236      * @since PDF 1.4
237      */
getOutputIntents()238     public PDFArray getOutputIntents() {
239         return (PDFArray)get("OutputIntents");
240     }
241 
242     /**
243      * Adds an OutputIntent to the PDF
244      * @param outputIntent the OutputIntent dictionary
245      * @since PDF 1.4
246      */
addOutputIntent(PDFOutputIntent outputIntent)247     public void addOutputIntent(PDFOutputIntent outputIntent) {
248         if (getDocumentSafely().getPDFVersion().compareTo(Version.V1_4) >= 0) {
249             PDFArray outputIntents = getOutputIntents();
250             if (outputIntents == null) {
251                 outputIntents = new PDFArray(this);
252                 put("OutputIntents", outputIntents);
253             }
254             outputIntents.add(outputIntent);
255         }
256     }
257 
258     /**
259      * Sets the "Version" entry. If this version is greater than that specified in the header, this
260      * version takes precedence.
261      *
262      * @param version the PDF document version
263      * @since PDF 1.4
264      */
setVersion(Version version)265     void setVersion(Version version) {
266         put("Version", new PDFName(version.toString()));
267     }
268 
269     /**
270      * Returns the language identifier of the document.
271      * @return the language identifier of the document (or null if not set or undefined)
272      * @since PDF 1.4
273      */
getLanguage()274     public String getLanguage() {
275         return (String)get("Lang");
276     }
277 
278     /**
279      * Sets the locale of the document.
280      * @param locale the locale of the document.
281      */
setLanguage(Locale locale)282     public void setLanguage(Locale locale) {
283         if (locale == null) {
284             throw new NullPointerException("locale must not be null");
285         }
286         setLanguage(LanguageTags.toLanguageTag(locale));
287     }
288 
setLanguage(String lang)289     private void setLanguage(String lang) {
290         put("Lang", lang);
291     }
292 
293     /**
294      * Sets the StructTreeRoot object. Used for accessibility.
295      * @param structTreeRoot of this document
296      */
setStructTreeRoot(PDFStructTreeRoot structTreeRoot)297     public void setStructTreeRoot(PDFStructTreeRoot structTreeRoot) {
298         if (structTreeRoot == null) {
299             throw new NullPointerException("structTreeRoot must not be null");
300         }
301         put("StructTreeRoot", structTreeRoot);
302     }
303 
304     /**
305      * Returns the StructTreeRoot object.
306      * @return the structure tree root (or null if accessibility is not enabled)
307      */
getStructTreeRoot()308     public PDFStructTreeRoot getStructTreeRoot() {
309         return (PDFStructTreeRoot)get("StructTreeRoot");
310     }
311 
312     /**
313      * Marks this document as conforming to the Tagged PDF conventions.
314      */
makeTagged()315     public void makeTagged() {
316         PDFDictionary dict = new PDFDictionary();
317         dict.put("Marked", Boolean.TRUE);
318         put("MarkInfo", dict);  //new PDFMarkInfo()
319     }
320 
321     /**
322      * Returns the MarkInfo dictionary.
323      * @return the MarkInfo dictionary (or null if it's not present)
324      */
getMarkInfo()325     public PDFDictionary getMarkInfo() {
326         return (PDFDictionary)get("MarkInfo");
327     }
328 
getDPartRoot()329     public PDFDPartRoot getDPartRoot() {
330         if (dPartRoot == null) {
331             dPartRoot = getDocument().getFactory().makeDPartRoot();
332             put("DPartRoot", dPartRoot.makeReference());
333         }
334         return dPartRoot;
335     }
336 
addAF(PDFFileSpec fileSpec)337     public void addAF(PDFFileSpec fileSpec) {
338         if (af == null) {
339             af = new PDFArray();
340             put("AF", af);
341         }
342         af.add(fileSpec);
343         fileSpec.put("AFRelationship", new PDFName("Data"));
344     }
345 }
346