org.jpedal.objects.layers.PdfLayerList Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of OpenViewerFX Show documentation
Show all versions of OpenViewerFX Show documentation
An Open Source JavaFX PDF Viewer
/*
* ===========================================
* Java Pdf Extraction Decoding Access Library
* ===========================================
*
* Project Info: http://www.idrsolutions.com
* Help section for developers at http://www.idrsolutions.com/support/
*
* (C) Copyright 1997-2016 IDRsolutions and Contributors.
*
* This file is part of JPedal/JPDF2HTML5
*
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---------------
* PdfLayerList.java
* ---------------
*/
package org.jpedal.objects.layers;
import java.util.*;
import org.jpedal.io.PdfObjectReader;
import org.jpedal.objects.raw.OCObject;
import org.jpedal.objects.raw.PdfDictionary;
import org.jpedal.objects.raw.PdfKeyPairsIterator;
import org.jpedal.objects.raw.PdfObject;
public class PdfLayerList {
private static boolean debug;
/**page we have outlines for*/
private int OCpageNumber=-1;
private String padding ="";
//used in tree as unique separator
public static final char deliminator=(char)65535;
private final Map layerNames=new LinkedHashMap();
private final Map streamToName=new HashMap();
private final Map layersEnabled=new HashMap();
private Map jsCommands;
private final Map metaData=new HashMap();
private final Map layersTested=new HashMap();
private final Map layerLocks=new HashMap();
private boolean changesMade;
private Map propertyMap;
private Map refToPropertyID;
private Map refTolayerName;
private Map RBconstraints;
private final Map minScale=new HashMap();
private final Map maxScale=new HashMap();
//private float scaling=1f;
private int layerCount;
private Object[] order;
private PdfObjectReader currentPdfFile;
private Layer[] layers;
/**
* add layers and settings to list
* @param OCProperties is of type PdfObject
* @param PropertiesObj is of type PdfObject
* @param currentPdfFile is of type PdfObjectReader
* @param pageNumber is of type int
*/
public void init(final PdfObject OCProperties, final PdfObject PropertiesObj, final PdfObjectReader currentPdfFile, final int pageNumber) {
OCpageNumber=pageNumber;
propertyMap=new HashMap();
refToPropertyID =new HashMap();
refTolayerName=new HashMap();
RBconstraints=new HashMap();
this.currentPdfFile=currentPdfFile;
if(PropertiesObj!=null) {
setupOCMaps(PropertiesObj, currentPdfFile);
}
final PdfObject layerDict=OCProperties.getDictionary(PdfDictionary.D);
if(layerDict==null) {
return;
}
int OCBaseState=layerDict.getNameAsConstant(PdfDictionary.BaseState);
//if not set use default
if(OCBaseState== PdfDictionary.Unknown) {
OCBaseState = PdfDictionary.ON;
}
//read order first and may be over-written by ON/OFF
order=layerDict.getObjectArray(PdfDictionary.Order);
if(debug){
System.out.println("PropertiesObj="+PropertiesObj);
System.out.println("layerDict="+layerDict);
System.out.println("propertyMap="+propertyMap);
System.out.println("propertyMap="+propertyMap);
System.out.println("refToPropertyID="+refToPropertyID);
System.out.println("refTolayerName="+refTolayerName);
System.out.println("OCBaseState="+OCBaseState+" (ON="+ PdfDictionary.ON+ ')');
System.out.println("order="+Arrays.toString(order));
showValues("ON=",PdfDictionary.ON,layerDict);
showValues("OFF=",PdfDictionary.OFF,layerDict);
showValues("RBGroups=",PdfDictionary.RBGroups,layerDict);
}
/*
* workout list of layers (can be in several places)
*/
addLayer(OCBaseState, order,null);
//read the ON and OFF values
if(OCBaseState!=PdfDictionary.ON) //redundant if basestate on
{
addLayer(PdfDictionary.ON, layerDict.getKeyArray(PdfDictionary.ON), null);
}
if(OCBaseState!=PdfDictionary.OFF) //redundant if basestate off
{
addLayer(PdfDictionary.OFF, layerDict.getKeyArray(PdfDictionary.OFF), null);
}
/*
* handle case where layers not explicitly switched on
*/
if(OCBaseState==PdfDictionary.ON){// && layerDict.getKeyArray(PdfDictionary.OFF)==null){
final Iterator keys=refToPropertyID.keySet().iterator();
String ref;String layerName;
while(keys.hasNext()){
ref = keys.next();
layerName=refToPropertyID.get(ref);
refTolayerName.put(ref,layerName);
if(! layersTested.containsKey(layerName)){
layersTested.put(layerName,"x");
layersEnabled.put(layerName,"x");
}
}
}
//set any locks
setLocks(currentPdfFile,layerDict.getKeyArray(PdfDictionary.Locked));
//any constraints
setConstraints(layerDict.getKeyArray(PdfDictionary.RBGroups));
//any Additional Dictionaries
setAS(layerDict.getKeyArray(PdfDictionary.AS), currentPdfFile);
/*
* read any metadata
*/
final int[] keys={PdfDictionary.Name,PdfDictionary.Creator};
final String[] titles={"Name","Creator"};
final int count=keys.length;
String val;
for(int jj=0;jj1 && rawRef[rawRef.length-1]=='R'){
nextObject=new OCObject(ref);
currentPdfFile.readObject(nextObject);
name=ref;
}else{ //it is a name for the level so add into path of name
if(parentName==null) {
parentName = ref;
} else {
parentName = ref + deliminator + parentName;
}
}
}
if(nextObject!=null){
layerCount++;
layerName=nextObject.getTextStreamValue(PdfDictionary.Name);
if(parentName!=null) {
layerName = layerName + deliminator + parentName;
}
if(debug) {
System.out.println(padding + "[layer1] add layer=" + layerName + " ref=" + ref + " parent=" + parentName + " refToLayerName=" + refTolayerName.get(ref) + " ref=" + ref);
}
refTolayerName.put(ref,layerName);
//and write back name value
layer[ii]=layerName;
layerNames.put(layerName, status);
if(name.indexOf(',')==-1){
final String oldValue= streamToName.get(name);
if(oldValue==null) {
streamToName.put(name, layerName);
} else {
streamToName.put(name, oldValue + ',' + layerName);
}
}else{
final StringTokenizer names=new StringTokenizer(name,",");
while(names.hasMoreTokens()){
name=names.nextToken();
final String oldValue= streamToName.get(name);
if(oldValue==null) {
streamToName.put(name, layerName);
} else {
streamToName.put(name, oldValue + ',' + layerName);
}
}
}
//must be done as can be defined in order with default and then ON/OFF as well
if(status==PdfDictionary.ON){
layersEnabled.put(layerName,"x");
}else{
layersEnabled.remove(layerName);
}
}
}else {
addLayer(status, (Object[]) layer[ii], layerName);
}
}
if(debug){
final int len=padding.length();
if(len>3) {
padding = padding.substring(0, len - 3);
}
}
}
private void addLayer(final int status, final byte[][] layer, final String parentName) {
if(layer ==null) {
return;
}
String ref,name;
PdfObject nextObject;
for (final byte[] aLayer : layer) {
ref = new String(aLayer);
name = refToPropertyID.get(ref);
nextObject = propertyMap.get(ref);
if (nextObject != null) {
layerCount++;
String layerName = nextObject.getTextStreamValue(PdfDictionary.Name);
if (parentName != null) {
layerName = layerName + deliminator + parentName;
}
//pick up full name set by Order
if (status == PdfDictionary.ON || status == PdfDictionary.OFF) {
final String possName = refTolayerName.get(ref);
if (possName != null) {
layerName = possName;
}
}
if (debug) {
System.out.println(padding + "[layer0] add layer=" + layerName + " ref=" + ref + " parent=" + parentName + " refToLayerName=" + refTolayerName.get(ref) + " status=" + status);
}
if (refTolayerName.get(ref) == null) {
refTolayerName.put(ref, layerName);
layerNames.put(layerName, status);
}
if (streamToName.get(name) != null) {//ignore if done
} else if (name.indexOf(',') == -1) {
final String oldValue = streamToName.get(name);
if (oldValue == null) {
streamToName.put(name, layerName);
} else {
streamToName.put(name, oldValue + ',' + layerName);
}
} else {
final StringTokenizer names = new StringTokenizer(name, ",");
while (names.hasMoreTokens()) {
name = names.nextToken();
final String oldValue = streamToName.get(name);
if (oldValue == null) {
streamToName.put(name, layerName);
} else {
streamToName.put(name, oldValue + ',' + layerName);
}
}
}
//must be done as can be defined in order with default and then ON/OFF as well
if (status == PdfDictionary.ON) {
layersEnabled.put(layerName, "x");
} else {
layersEnabled.remove(layerName);
}
layersTested.put(layerName, "x");
}
}
}
private void setAS(final byte[][] AS, final PdfObjectReader currentPdfFile) {
if(AS ==null) {
return;
}
int event;
String ref, name,layerName;
byte[][] OCGs;
PdfObject nextObject;
for (final byte[] A : AS) {
//can also be a direct command which is not yet implemented
if (A == null) {
continue;
}
ref = new String(A);
nextObject = new OCObject(ref);
if (A[0] == '<') {
nextObject.setStatus(PdfObject.UNDECODED_DIRECT);
} else {
nextObject.setStatus(PdfObject.UNDECODED_REF);
}
//must be done AFTER setStatus()
nextObject.setUnresolvedData(A, PdfDictionary.AS);
currentPdfFile.checkResolved(nextObject);
event = nextObject.getParameterConstant(PdfDictionary.Event);
if (nextObject != null) {
if (event == PdfDictionary.View) {
OCGs = nextObject.getKeyArray(PdfDictionary.OCGs);
if (OCGs != null) {
for (final byte[] OCG : OCGs) {
ref = new String(OCG);
nextObject = new OCObject(ref);
if (OCG[0] == '<') {
nextObject.setStatus(PdfObject.UNDECODED_DIRECT);
} else {
nextObject.setStatus(PdfObject.UNDECODED_REF);
}
//must be done AFTER setStatus()
nextObject.setUnresolvedData(OCG, PdfDictionary.OCGs);
currentPdfFile.checkResolved(nextObject);
layerName = nextObject.getTextStreamValue(PdfDictionary.Name);
name = refToPropertyID.get(ref);
if(name==null && refToPropertyID.isEmpty()){ //23911 - include implicit value if Properties not set
name="MC0";
}
streamToName.put(name, layerName);
//System.out.println((char)OCGs[jj][0]+" "+ref+" "+" "+nextObject+" "+nextObject.getTextStreamValue(PdfDictionary.Name));
final PdfObject usageObj = nextObject.getDictionary(PdfDictionary.Usage);
if (usageObj != null) {
final PdfObject zoomObj = usageObj.getDictionary(PdfDictionary.Zoom);
//set zoom values
if (zoomObj != null) {
final float min = zoomObj.getFloatNumber(PdfDictionary.min);
if (min != 0) {
minScale.put(layerName, min);
}
final float max = zoomObj.getFloatNumber(PdfDictionary.max);
if (max != 0) {
maxScale.put(layerName, max);
}
}
}
}
}
}
//layerCount++;
//String layerName=nextObject.getTextStreamValue(PdfDictionary.Name);
//if(debug)
//System.out.println("[AS] add AS="+layerName);
//refTolayerName.put(ref,layerName);
//layerNames.put(layerName,new Integer(status));
// if(layerName.indexOf(",")==-1){
// String oldValue=(String)streamToName.get(layerName);
// if(oldValue==null)
// streamToName.put(layerName,layerName);
// else
// streamToName.put(layerName,oldValue+","+layerName);
// }else{
// StringTokenizer names=new StringTokenizer(layerName,",");
// while(names.hasMoreTokens()){
// layerName=names.nextToken();
// String oldValue=(String)streamToName.get(layerName);
// if(oldValue==null)
// streamToName.put(layerName,layerName);
// else
// streamToName.put(layerName,oldValue+","+layerName);
// }
// }
}
}
}
private void setLocks(final PdfObjectReader currentPdfFile, final byte[][] layer) {
if(layer ==null) {
return;
}
for (final byte[] aLayer : layer) {
final String nextValue = new String(aLayer);
final PdfObject nextObject = new OCObject(nextValue);
currentPdfFile.readObject(nextObject);
final String layerName = nextObject.getTextStreamValue(PdfDictionary.Name);
layerLocks.put(layerName, "x");
}
}
public Map getMetaData() {
return Collections.unmodifiableMap(metaData);
}
public Object[] getDisplayTree(){
if(order!=null) {
return order;
} else {
return getNames();
}
}
/**
* return list of layer names as String array
*/
private String[] getNames() {
final int count=layerNames.size();
final String[] nameList=new String[count];
final Iterator names=layerNames.keySet().iterator();
int jj=0;
while(names.hasNext()){
nameList[jj]=names.next();
jj++;
}
return nameList;
}
/**
* will display only these layers and hide all others and will override
* any constraints.
* If you pass null in, all layers will be removed
* @param layerNames is of type String[]
*/
@SuppressWarnings("UnusedDeclaration")
public void setVisibleLayers(final String[] layerNames) {
layersEnabled.clear();
if(layerNames!=null){
for (final String layerName : layerNames) {
layersEnabled.put(layerName, "x");
}
}
//flag it has been altered
changesMade=true;
}
/**
* Used internally only. takes name in Stream (ie MC7 and works out if we
* need to decode) if isID==true.
*
* @param name is of type String
* @param isID is of type boolean
* @return type boolean
*/
public boolean decodeLayer(final String name, final boolean isID) {
if(layerCount==0) {
return true;
}
boolean isLayerVisible =false;
String layerName=name;
//see if match found otherwise assume name
if(isID){
final String mappedName= streamToName.get(name);
if(mappedName!=null) {
layerName = mappedName;
}
}
if(layerName ==null) {
return false;
} else{
//if multiple layers them comma separated list
if(layerName.indexOf(',')==-1){
isLayerVisible =layersEnabled.containsKey(layerName);
if(isLayerVisible) {
isLayerVisible = hiddenByParent(isLayerVisible, layerName);
}
}else{
final StringTokenizer names=new StringTokenizer(layerName,",");
while(names.hasMoreTokens()){
final String nextName=names.nextToken();
isLayerVisible =layersEnabled.containsKey(nextName);
if(isLayerVisible) {
isLayerVisible = hiddenByParent(isLayerVisible, nextName);
}
if(isLayerVisible) //exit on first match
{
break;
}
}
}
if(debug) {
System.out.println("[isVisible] " + name + " decode=" + isLayerVisible + " enabled=" + layersEnabled + " layerName=" + layerName + " isEnabled=" + this.layersEnabled);
}
//System.out.println("stream="+streamToName);
return isLayerVisible;
}
}
//check not disabled by Parent up tree
private boolean hiddenByParent(boolean layerVisible, String layerName) {
int id=layerName.indexOf(deliminator);
if(layerVisible && id!=-1){
String parent= layerName.substring(id+1,layerName.length());
while(parent!=null && layerVisible && isLayerName(parent)){
layerVisible =decodeLayer(parent,false);
layerName=parent;
id=layerName.indexOf(deliminator);
if(id==-1) {
parent = null;
} else {
parent = layerName.substring(id + 1, layerName.length());
}
}
}
return layerVisible;
}
/**
* Switch on/off layers based on Zoom.
* @param scaling is of type float
* @return is of type boolean
*/
public boolean setZoom(final float scaling) {
String layerName;
final Iterator minZoomLayers=minScale.keySet().iterator();
while(minZoomLayers.hasNext()){
layerName= minZoomLayers.next();
final Float minScalingValue= minScale.get(layerName);
//Zoom off
if(minScalingValue!=null){
//System.out.println(layerName+" "+scaling+" "+minScalingValue);
if(scaling< minScalingValue){
layersEnabled.remove(layerName);
changesMade=true;
}else if(!layersEnabled.containsKey(layerName)){
layersEnabled.put(layerName,"x");
changesMade=true;
}
}
}
final Iterator maxZoomLayers=maxScale.keySet().iterator();
while(maxZoomLayers.hasNext()){
layerName= minZoomLayers.next();
final Float maxScalingValue= maxScale.get(layerName);
if(maxScalingValue!=null){
if(scaling> maxScalingValue){
layersEnabled.remove(layerName);
changesMade=true;
}else if(!layersEnabled.containsKey(layerName)){
layersEnabled.put(layerName,"x");
changesMade=true;
}
}
}
return changesMade;
}
public boolean isVisible(final String layerName) {
return layersEnabled.containsKey(layerName);
}
public void setVisiblity(final String layerName, final boolean isVisible) {
if(debug) {
System.out.println("[layer] setVisiblity=" + layerName + " isVisible=" + isVisible);
}
if(isVisible){
layersEnabled.put(layerName,"x");
//disable any other layers
final String layersToDisable= RBconstraints.get(layerName);
if(layersToDisable!=null){
final StringTokenizer layers=new StringTokenizer(layersToDisable,",");
while(layers.hasMoreTokens()) {
layersEnabled.remove(layers.nextToken());
}
}
}else {
layersEnabled.remove(layerName);
}
//flag it has been altered
changesMade=true;
}
public boolean isVisible(final PdfObject XObject) {
//see if visible
boolean isVisible = true;
//if layer object attached see if should be visible
final PdfObject layerObj = XObject.getDictionary(PdfDictionary.OC);
if (layerObj != null) {
String layerName=null;
final PdfObject OCGs_as_dictionary=layerObj.getDictionary(PdfDictionary.OCGs);
if(OCGs_as_dictionary!=null){ //look at viewtate first
/*
* NOTE!!!! Print and other modes not implemented yet
* (just added what I needed to fix 17584)
*/
//check viewmode flag
final PdfObject usage=OCGs_as_dictionary.getDictionary(PdfDictionary.Usage);
if(usage!=null){
final PdfObject viewState=usage.getDictionary(PdfDictionary.View);
if(viewState!=null){
isVisible=viewState.getNameAsConstant(PdfDictionary.ViewState)==PdfDictionary.ON;
}
}
}else{
final byte[][] OCGS = layerObj.getKeyArray(PdfDictionary.OCGs);
if(OCGS!=null){
for (final byte[] OCG : OCGS) {
final String ref = new String(OCG);
layerName = getNameFromRef(ref);
}
}
if(layerName==null) {
layerName = layerObj.getTextStreamValue(PdfDictionary.Name);
}
if (layerName != null && isLayerName(layerName)) {
isVisible = isVisible(layerName);
}
}
}
return isVisible;
}
public boolean isLocked(final String layerName) {
return layerLocks.containsKey(layerName);
}
/**
* show if decoded version match visibility flags which can be altered by
* user
*
* @return type boolean
*/
public boolean getChangesMade() {
return changesMade;
}
/**
* show if is name of layer (as opposed to just label).
*
* @param name is of type String
* @return is of type boolean
*/
public boolean isLayerName(final String name) {
return layerNames.containsKey(name);
}
/**
* number of layers setup.
*
* @return is of type int.
*/
public int getLayersCount() {
return layerCount;
}
public String getNameFromRef(final String ref) {
return refTolayerName.get(ref);
}
// public void setScaling(float scaling) {
// this.scaling=scaling;
// }
/**JS
* Gets an array of OCG objects found on a specified page.
*
* @return - An array of OCG objects or null if no OCGs are present.
*/
public Object[] getOCGs(){
//return once initialised
if(layers!=null) {
return layers;
}
final int count=layerNames.size();
//create array of values with access to this so we can reset
final Layer[] layers=new Layer[count];
final Iterator layersIt=layerNames.keySet().iterator();
int ii=0;
String name;
while(layersIt.hasNext()){
name= layersIt.next();
layers[ii]=new Layer(name,this);
ii++;
}
return layers;
}
public void addJScommand(final String name, final String js) {
if(jsCommands==null) {
jsCommands = new HashMap();
}
//add to list to execute
jsCommands.put(name,js);
}
public Iterator getJSCommands() {
if(jsCommands!=null){
final Iterator names= this.jsCommands.keySet().iterator();
final Map visibleJSCommands=new HashMap();
while(names.hasNext()){
final String name= names.next();
if(this.isVisible(name)){
visibleJSCommands.put(jsCommands.get(name), "x");
}
}
return visibleJSCommands.keySet().iterator();
}else {
return null;
}
}
public int getOCpageNumber() {
return OCpageNumber;
}
}