/* * Copyright 2006-2017 ICEsoft Technologies Canada Corp. * * Licensed 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. */ package org.icepdf.core.pobjects; import org.icepdf.core.util.Library; import org.icepdf.core.util.Utils; import java.util.*; /** * The optional OCProperties entry in the document catalog * (see 7.7.2, "Document Catalog") shall contain, when present, the optional * content properties dictionary, which contains a list of all the optional * content groups in the document, as well as information about the default * and alternate configurations for optional content. This dictionary shall be * present if the file contains any optional content; if it is missing, a * conforming reader shall ignore any optional content structures in the document. * * @since 5.0 */ public class OptionalContent extends Dictionary { private Map<Reference, OptionalContentGroup> groups; public static final Name OCGs_KEY = new Name("OCGs"); public static final Name OC_KEY = new Name("OC"); public static final Name D_KEY = new Name("D"); public static final Name BASE_STATE_KEY = new Name("BaseState"); public static final Name INTENT_KEY = new Name("Intent"); public static final Name AS_KEY = new Name("AS"); public static final Name ORDER_KEY = new Name("Order"); public static final Name LIST_MODE_KEY = new Name("ListMode"); public static final Name RBGROUPS_KEY = new Name("RBGroups"); public static final Name LOCKED_KEY = new Name("Locked"); public static final Name OFF_VALUE = new Name("OFF"); public static final Name ON_vALUE = new Name("ON"); public static final Name UNCHANGED_KEY = new Name("Unchanged"); public static final Name VIEW_VALUE = new Name("View"); public static final Name DESIGN_VALUE = new Name("Design"); public static final Name NONE_OC_FLAG = new Name("marked"); private Name baseState = ON_vALUE; /** * A single intent name or an array containing any combination of names. it * shall be used to determine which optional content groups’ states to consider * and which to ignore in calculating the visibility of content * (see 8.11.2.3, "Intent"). * <br> * PDF defines two intent names, View and Design. In addition, the name All * shall indicate the set of all intents, including those not yet defined. * Default value: View. The value shall be View for the document’s default configuration. */ private List<Name> intent = Arrays.asList(VIEW_VALUE); /** * An array specifying the order for presentation of optional content groups * in a conforming reader’s user interface. The array elements may include * the following objects: * <br> * Optional content group dictionaries, whose Name entry shall be displayed in * the user interface by the conforming reader. * <br> * Arrays of optional content groups which may be displayed by a conforming * reader in a tree or outline structure. Each nested array may optionally * have as its first element a text string to be used as a non-selectable * label in a conforming reader’s user interface. * <br> * Text labels in nested arrays shall be used to present collections of * related optional content groups, and not to communicate actual nesting of * content inside multiple layers of groups (see EXAMPLE 1 in 8.11.4.3, * "Optional Content Configuration Dictionaries"). To reflect actual nesting * of groups in the content, such as for layers with sublayers, nested arrays * of groups without a text label shall be used (see EXAMPLE 2 in 8.11.4.3, * "Optional Content Configuration Dictionaries"). * <br> * An empty array [] explicitly specifies that no groups shall be presented. * In the default configuration dictionary, the default value shall be an empty * array; in other configuration dictionaries, the default shall be the Order * value from the default configuration dictionary. * <br> * Any groups not listed in this array shall not be presented in any user * interface that uses the configuration. */ private List<Object> order; private List<Object> rbGroups; // object was created but the PDF doesn't actually have optional content definition and optional content // properties may no longer be valid. private boolean emptyDefinition; public OptionalContent(Library l, HashMap h) { super(l, h); groups = new HashMap<Reference, OptionalContentGroup>(); } @Override @SuppressWarnings("unchecked") public void init() { if (inited) { return; } // test of a valid definition. if (entries == null || entries.size() == 0){ emptyDefinition = true; } // build out the optionContentGroups from the OCGs array, array should always // be indirect references to the optionContentGroups. Object ogcs = library.getObject(entries, OCGs_KEY); if (ogcs instanceof List) { List ogcList = (List) ogcs; Reference ref; Object ocgObj; for (Object object : ogcList) { if (object instanceof Reference) { ref = (Reference) object; ocgObj = library.getObject(ref); if (ocgObj instanceof OptionalContentGroup) { OptionalContentGroup ogc = (OptionalContentGroup) ocgObj; groups.put(ref, ogc); } } } } // The default viewing optional content configuration dictionary. Object dObj = library.getObject(entries, D_KEY); if (dObj instanceof HashMap) { HashMap configurationDictionary = (HashMap) dObj; // apply the base state ON|OFF|Unchanged Object tmp = library.getName(configurationDictionary, BASE_STATE_KEY); if (tmp != null && tmp instanceof Name) { baseState = (Name) tmp; } // If the BaseState entry is ON, then we only need to look at the OFF // entries. boolean isBaseOn = baseState.equals(ON_vALUE); List toggle; if (isBaseOn) { toggle = library.getArray(configurationDictionary, OFF_VALUE); } // If the BaseState entry is OFF, then the ON entries are relevant. else { toggle = library.getArray(configurationDictionary, ON_vALUE); } // build out the if (toggle != null) { for (Object obj : toggle) { OptionalContentGroup ocg = groups.get(obj); if (ocg != null) { if (isBaseOn) { // remove the off entries ocg.setVisible(false); } else { // otherwise we add the on entries. ocg.setVisible(true); } } } } // check for an intent entry tmp = library.getName(configurationDictionary, INTENT_KEY); if (tmp != null) { if (tmp instanceof Name) { intent = Arrays.asList(new Name[]{(Name) tmp}); } else if (tmp instanceof List) { intent = (List) tmp; } } // ignore AS for now. /** * An array of usage application dictionaries (see Table 103) * specifying which usage dictionary categories (see Table 102) * shall be consulted by conforming readers to automatically set the * states of optional content groups based on external factors, * such as the current system language or viewing magnification, * and when they shall be applied.Order */ // get the ordering information used by the UI. resolve the ref // tmp = library.getObject(configurationDictionary, ORDER_KEY); if (tmp != null && tmp instanceof List) { List orderedOCs = (List) tmp; if (orderedOCs.size() > 0) { order = new ArrayList<Object>(orderedOCs.size()); order = parseOrderArray(orderedOCs, null); } } // get the radio button group data for correct UI behavior . tmp = library.getObject(configurationDictionary, RBGROUPS_KEY); if (tmp != null && tmp instanceof List) { List orderedOCs = (List) tmp; if (orderedOCs.size() > 0) { rbGroups = new ArrayList<Object>(orderedOCs.size()); rbGroups = parseOrderArray(orderedOCs, null); } } // ignore Locked for now } inited = true; } @SuppressWarnings("unchecked") private List<Object> parseOrderArray(List<Object> rawOrder, OptionalContentGroup parent) { List<Object> order = new ArrayList<Object>(5); OptionalContentGroup group = null; for (Object obj : rawOrder) { if (obj instanceof Reference) { Object refObject = getOCGs((Reference) obj); if (refObject != null) { group = (OptionalContentGroup) refObject; if (parent != null && !parent.isVisible()) { group.setVisible(false); } order.add(group); } else { obj = library.getObject((Reference) obj); } } if (obj instanceof List) { parent = group; order.add(parseOrderArray((List) obj, parent)); } else if (obj instanceof StringObject) { order.add(Utils.convertStringObject(library, (StringObject) obj)); } } return order; } public boolean isVisible(Reference ocgRef) { Object object = library.getObject(ocgRef); if (object instanceof OptionalContentGroup) { return isVisible((OptionalContentGroup) object); } else if (object instanceof OptionalContentMembership) { return isVisible((OptionalContentMembership) object); } return false; } public boolean isVisible(OptionalContentGroup ocg) { return groups.containsKey(ocg.getPObjectReference()); } public boolean isVisible(OptionalContentMembership ocmd) { ocmd.init(); return ocmd.isVisible(); } /** * Test if an xForm object image or content is visible. * * @param object content to check visibility. * @return optional content groups currently visibility state, returns * true if no state can be found, better to show then to * hide by default. */ public boolean isVisible(Object object) { if (object instanceof Reference) { return isVisible((Reference) object); } else if (object instanceof OptionalContentGroup) { return isVisible((OptionalContentGroup) object); } else if (object instanceof OptionalContentMembership) { return isVisible((OptionalContentMembership) object); } return true; } public List<Object> getOrder() { return order; } public List<Name> getIntent() { return intent; } public int getGroupsSize() { return groups.size(); } public List<Object> getRbGroups() { return rbGroups; } public OptionalContentGroup getOCGs(Reference reference) { return groups.get(reference); } public boolean isEmptyDefinition() { return emptyDefinition; } }