org.apache.pdfbox.pdmodel.PDResources Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of pdfbox Show documentation
Show all versions of pdfbox Show documentation
The Apache PDFBox library is an open source Java tool for working with PDF documents.
/*
* 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.
*/
package org.apache.pdfbox.pdmodel;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.apache.pdfbox.cos.COSBase;
import org.apache.pdfbox.cos.COSDictionary;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.cos.COSObject;
import org.apache.pdfbox.cos.COSStream;
import org.apache.pdfbox.pdmodel.common.COSObjectable;
import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDPropertyList;
import org.apache.pdfbox.pdmodel.font.PDFont;
import org.apache.pdfbox.pdmodel.font.PDFontFactory;
import org.apache.pdfbox.pdmodel.graphics.color.PDPattern;
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
import org.apache.pdfbox.pdmodel.graphics.optionalcontent.PDOptionalContentGroup;
import org.apache.pdfbox.pdmodel.graphics.state.PDExtendedGraphicsState;
import org.apache.pdfbox.pdmodel.graphics.color.PDColorSpace;
import org.apache.pdfbox.pdmodel.graphics.pattern.PDAbstractPattern;
import org.apache.pdfbox.pdmodel.graphics.shading.PDShading;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
/**
* A set of resources available at the page/pages/stream level.
*
* @author Ben Litchfield
* @author John Hewson
*/
public final class PDResources implements COSObjectable
{
private final COSDictionary resources;
private final ResourceCache cache;
// PDFBOX-3442 cache fonts that are not indirect objects, as these aren't cached in ResourceCache
// and this would result in huge memory footprint in text extraction
private final Map > directFontCache =
new HashMap>();
/**
* Constructor for embedding.
*/
public PDResources()
{
resources = new COSDictionary();
cache = null;
}
/**
* Constructor for reading.
*
* @param resourceDictionary The cos dictionary for this resource.
*/
public PDResources(COSDictionary resourceDictionary)
{
if (resourceDictionary == null)
{
throw new IllegalArgumentException("resourceDictionary is null");
}
resources = resourceDictionary;
cache = null;
}
/**
* Constructor for reading.
*
* @param resourceDictionary The cos dictionary for this resource.
* @param resourceCache The document's resource cache, may be null.
*/
public PDResources(COSDictionary resourceDictionary, ResourceCache resourceCache)
{
if (resourceDictionary == null)
{
throw new IllegalArgumentException("resourceDictionary is null");
}
resources = resourceDictionary;
cache = resourceCache;
}
/**
* Returns the underlying dictionary.
*/
@Override
public COSDictionary getCOSObject()
{
return resources;
}
/**
* Returns the font resource with the given name, or null if none exists.
*
* @param name Name of the font resource.
*
* @return the font resource with the given name.
*
* @throws IOException if something went wrong.
*/
public PDFont getFont(COSName name) throws IOException
{
COSObject indirect = getIndirect(COSName.FONT, name);
if (cache != null && indirect != null)
{
PDFont cached = cache.getFont(indirect);
if (cached != null)
{
return cached;
}
}
else if (indirect == null)
{
SoftReference ref = directFontCache.get(name);
if (ref != null)
{
PDFont cached = ref.get();
if (cached != null)
{
return cached;
}
}
}
PDFont font = null;
COSDictionary dict = (COSDictionary)get(COSName.FONT, name);
if (dict != null)
{
font = PDFontFactory.createFont(dict);
}
if (cache != null && indirect != null)
{
cache.put(indirect, font);
}
else if (indirect == null)
{
directFontCache.put(name, new SoftReference(font));
}
return font;
}
/**
* Returns the color space resource with the given name, or null if none exists.
*
* @param name Name of the color space resource.
* @return a new color space.
* @throws IOException if something went wrong.
*/
public PDColorSpace getColorSpace(COSName name) throws IOException
{
return getColorSpace(name, false);
}
/**
* Returns the color space resource with the given name, or null if none exists. This method is
* for PDFBox internal use only, others should use {@link #getColorSpace(COSName)}.
*
* @param name Name of the color space resource.
* @param wasDefault if current color space was used by a default color space. This parameter is
* to
* @return a new color space.
* @throws IOException if something went wrong.
*/
public PDColorSpace getColorSpace(COSName name, boolean wasDefault) throws IOException
{
COSObject indirect = getIndirect(COSName.COLORSPACE, name);
if (cache != null && indirect != null)
{
PDColorSpace cached = cache.getColorSpace(indirect);
if (cached != null)
{
return cached;
}
}
// get the instance
PDColorSpace colorSpace;
COSBase object = get(COSName.COLORSPACE, name);
if (object != null)
{
colorSpace = PDColorSpace.create(object, this, wasDefault);
}
else
{
colorSpace = PDColorSpace.create(name, this, wasDefault);
}
// we can't cache PDPattern, because it holds page resources, see PDFBOX-2370
if (cache != null && !(colorSpace instanceof PDPattern))
{
cache.put(indirect, colorSpace);
}
return colorSpace;
}
/**
* Returns true if the given color space name exists in these resources.
*
* @param name Name of the color space resource.
*
* @return true if the color space with the given name exists.
*/
public boolean hasColorSpace(COSName name)
{
return get(COSName.COLORSPACE, name) != null;
}
/**
* Returns the extended graphics state resource with the given name, or null if none exists.
*
* @param name Name of the graphics state resource.
*
* @return the extended graphics state resource with the given name.
*/
public PDExtendedGraphicsState getExtGState(COSName name)
{
COSObject indirect = getIndirect(COSName.EXT_G_STATE, name);
if (cache != null && indirect != null)
{
PDExtendedGraphicsState cached = cache.getExtGState(indirect);
if (cached != null)
{
return cached;
}
}
// get the instance
PDExtendedGraphicsState extGState = null;
COSDictionary dict = (COSDictionary)get(COSName.EXT_G_STATE, name);
if (dict != null)
{
extGState = new PDExtendedGraphicsState(dict);
}
if (cache != null)
{
cache.put(indirect, extGState);
}
return extGState;
}
/**
* Returns the shading resource with the given name, or null if none exists.
*
* @param name Name of the shading resource.
*
* @return the shading resource of the given name.
*
* @throws IOException if something went wrong.
*/
public PDShading getShading(COSName name) throws IOException
{
COSObject indirect = getIndirect(COSName.SHADING, name);
if (cache != null && indirect != null)
{
PDShading cached = cache.getShading(indirect);
if (cached != null)
{
return cached;
}
}
// get the instance
PDShading shading = null;
COSDictionary dict = (COSDictionary)get(COSName.SHADING, name);
if (dict != null)
{
shading = PDShading.create(dict);
}
if (cache != null)
{
cache.put(indirect, shading);
}
return shading;
}
/**
* Returns the pattern resource with the given name, or null if none exists.
*
* @param name Name of the pattern resource.
*
* @return the pattern resource of the given name.
*
* @throws IOException if something went wrong.
*/
public PDAbstractPattern getPattern(COSName name) throws IOException
{
COSObject indirect = getIndirect(COSName.PATTERN, name);
if (cache != null && indirect != null)
{
PDAbstractPattern cached = cache.getPattern(indirect);
if (cached != null)
{
return cached;
}
}
// get the instance
PDAbstractPattern pattern = null;
COSDictionary dict = (COSDictionary)get(COSName.PATTERN, name);
if (dict != null)
{
pattern = PDAbstractPattern.create(dict);
}
if (cache != null)
{
cache.put(indirect, pattern);
}
return pattern;
}
/**
* Returns the property list resource with the given name, or null if none exists.
*
* @param name Name of the property list resource.
*
* @return the property list resource of the given name.
*/
public PDPropertyList getProperties(COSName name)
{
COSObject indirect = getIndirect(COSName.PROPERTIES, name);
if (cache != null && indirect != null)
{
PDPropertyList cached = cache.getProperties(indirect);
if (cached != null)
{
return cached;
}
}
// get the instance
PDPropertyList propertyList = null;
COSDictionary dict = (COSDictionary)get(COSName.PROPERTIES, name);
if (dict != null)
{
propertyList = PDPropertyList.create(dict);
}
if (cache != null)
{
cache.put(indirect, propertyList);
}
return propertyList;
}
/**
* Tells whether the XObject resource with the given name is an image.
*
* @param name Name of the XObject resource.
* @return true if it is an image XObject, false if not.
*/
public boolean isImageXObject(COSName name)
{
// get the instance
COSBase value = get(COSName.XOBJECT, name);
if (value == null)
{
return false;
}
else if (value instanceof COSObject)
{
value = ((COSObject) value).getObject();
}
if (!(value instanceof COSStream))
{
return false;
}
COSStream stream = (COSStream) value;
return COSName.IMAGE.equals(stream.getCOSName(COSName.SUBTYPE));
}
/**
* Returns the XObject resource with the given name, or null if none exists.
*
* @param name Name of the XObject resource.
*
* @return the XObject resource of the given name.
*
* @throws IOException if something went wrong.
*/
public PDXObject getXObject(COSName name) throws IOException
{
COSObject indirect = getIndirect(COSName.XOBJECT, name);
if (cache != null && indirect != null)
{
PDXObject cached = cache.getXObject(indirect);
if (cached != null)
{
return cached;
}
}
// get the instance
PDXObject xobject;
COSBase value = get(COSName.XOBJECT, name);
if (value == null)
{
xobject = null;
}
else if (value instanceof COSObject)
{
xobject = PDXObject.createXObject(((COSObject) value).getObject(), this);
}
else
{
xobject = PDXObject.createXObject(value, this);
}
if (cache != null && isAllowedCache(xobject))
{
cache.put(indirect, xobject);
}
return xobject;
}
private boolean isAllowedCache(PDXObject xobject)
{
if (xobject instanceof PDImageXObject)
{
COSBase colorSpace = xobject.getCOSObject().getDictionaryObject(COSName.COLORSPACE);
if (colorSpace instanceof COSName)
{
// don't cache if it might use page resources, see PDFBOX-2370 and PDFBOX-3484
COSName colorSpaceName = (COSName) colorSpace;
if (colorSpaceName.equals(COSName.DEVICECMYK) && hasColorSpace(COSName.DEFAULT_CMYK))
{
return false;
}
if (colorSpaceName.equals(COSName.DEVICERGB) && hasColorSpace(COSName.DEFAULT_RGB))
{
return false;
}
if (colorSpaceName.equals(COSName.DEVICEGRAY) && hasColorSpace(COSName.DEFAULT_GRAY))
{
return false;
}
if (hasColorSpace(colorSpaceName))
{
return false;
}
}
}
return true;
}
/**
* Returns the resource with the given name and kind as an indirect object, or null.
*/
private COSObject getIndirect(COSName kind, COSName name)
{
COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind);
if (dict == null)
{
return null;
}
COSBase base = dict.getItem(name);
if (base instanceof COSObject)
{
return (COSObject)base;
}
return null;
}
/**
* Returns the resource with the given name and kind, or null.
*/
private COSBase get(COSName kind, COSName name)
{
COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind);
if (dict == null)
{
return null;
}
return dict.getDictionaryObject(name);
}
/**
* Returns the names of the color space resources, if any.
*
* @return the names of all color space resources.
*/
public Iterable getColorSpaceNames()
{
return getNames(COSName.COLORSPACE);
}
/**
* Returns the names of the XObject resources, if any.
*
* @return the names of all XObject resources.
*/
public Iterable getXObjectNames()
{
return getNames(COSName.XOBJECT);
}
/**
* Returns the names of the font resources, if any.
*
* @return the names of all font resources.
*/
public Iterable getFontNames()
{
return getNames(COSName.FONT);
}
/**
* Returns the names of the property list resources, if any.
*
* @return the names of all property list resources.
*/
public Iterable getPropertiesNames()
{
return getNames(COSName.PROPERTIES);
}
/**
* Returns the names of the shading resources, if any.
*
* @return the names of all shading resources.
*/
public Iterable getShadingNames()
{
return getNames(COSName.SHADING);
}
/**
* Returns the names of the pattern resources, if any.
*
* @return the names of all pattern resources.
*/
public Iterable getPatternNames()
{
return getNames(COSName.PATTERN);
}
/**
* Returns the names of the extended graphics state resources, if any.
*
* @return the names of all extended graphics state resources.
*/
public Iterable getExtGStateNames()
{
return getNames(COSName.EXT_G_STATE);
}
/**
* Returns the resource names of the given kind.
*
* @return the names of all resources of the given kind.
*/
private Iterable getNames(COSName kind)
{
COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind);
if (dict == null)
{
return Collections.emptySet();
}
return dict.keySet();
}
/**
* Adds the given font to the resources of the current page and returns the name for the
* new resources. Returns the existing resource name if the given item already exists.
*
* @param font the font to add
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDFont font)
{
return add(COSName.FONT, "F", font);
}
/**
* Adds the given color space to the resources of the current page and returns the name for the
* new resources. Returns the existing resource name if the given item already exists.
*
* @param colorSpace the color space to add
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDColorSpace colorSpace)
{
return add(COSName.COLORSPACE, "cs", colorSpace);
}
/**
* Adds the given extended graphics state to the resources of the current page and returns the
* name for the new resources. Returns the existing resource name if the given item already exists.
*
* @param extGState the extended graphics state to add
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDExtendedGraphicsState extGState)
{
return add(COSName.EXT_G_STATE, "gs", extGState);
}
/**
* Adds the given shading to the resources of the current page and returns the name for the
* new resources. Returns the existing resource name if the given item already exists.
*
* @param shading the shading to add
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDShading shading)
{
return add(COSName.SHADING, "sh", shading);
}
/**
* Adds the given pattern to the resources of the current page and returns the name for the
* new resources. Returns the existing resource name if the given item already exists.
*
* @param pattern the pattern to add
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDAbstractPattern pattern)
{
return add(COSName.PATTERN, "p", pattern);
}
/**
* Adds the given property list to the resources of the current page and returns the name for
* the new resources. Returns the existing resource name if the given item already exists.
*
* @param properties the property list to add
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDPropertyList properties)
{
if (properties instanceof PDOptionalContentGroup)
{
return add(COSName.PROPERTIES, "oc", properties);
}
else
{
return add(COSName.PROPERTIES, "Prop", properties);
}
}
/**
* Adds the given image to the resources of the current page and returns the name for the
* new resources. Returns the existing resource name if the given item already exists.
*
* @param image the image to add
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDImageXObject image)
{
return add(COSName.XOBJECT, "Im", image);
}
/**
* Adds the given form to the resources of the current page and returns the name for the
* new resources. Returns the existing resource name if the given item already exists.
*
* @param form the form to add
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDFormXObject form)
{
return add(COSName.XOBJECT, "Form", form);
}
/**
* Adds the given XObject to the resources of the current page and returns the name for the
* new resources. Returns the existing resource name if the given item already exists.
*
* @param xobject the XObject to add
* @param prefix the prefix to be used when creating the resource name
* @return the name of the resource in the resources dictionary
*/
public COSName add(PDXObject xobject, String prefix)
{
return add(COSName.XOBJECT, prefix, xobject);
}
/**
* Adds the given resource if it does not already exist.
*/
private COSName add(COSName kind, String prefix, COSObjectable object)
{
// return the existing key if the item exists already
COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind);
if (dict != null && dict.containsValue(object.getCOSObject()))
{
return dict.getKeyForValue(object.getCOSObject());
}
// add the item with a new key
COSName name = createKey(kind, prefix);
put(kind, name, object);
return name;
}
/**
* Returns a unique key for a new resource.
*/
private COSName createKey(COSName kind, String prefix)
{
COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind);
if (dict == null)
{
return COSName.getPDFName(prefix + 1);
}
// find a unique key
String key;
int n = dict.keySet().size();
do
{
++n;
key = prefix + n;
}
while (dict.containsKey(key));
return COSName.getPDFName(key);
}
/**
* Sets the value of a given named resource.
*/
private void put(COSName kind, COSName name, COSObjectable object)
{
COSDictionary dict = (COSDictionary)resources.getDictionaryObject(kind);
if (dict == null)
{
dict = new COSDictionary();
resources.setItem(kind, dict);
}
dict.setItem(name, object);
}
/**
* Sets the font resource with the given name.
*
* @param name the name of the resource
* @param font the font to be added
*/
public void put(COSName name, PDFont font)
{
put(COSName.FONT, name, font);
}
/**
* Sets the color space resource with the given name.
*
* @param name the name of the resource
* @param colorSpace the color space to be added
*/
public void put(COSName name, PDColorSpace colorSpace)
{
put(COSName.COLORSPACE, name, colorSpace);
}
/**
* Sets the extended graphics state resource with the given name.
*
* @param name the name of the resource
* @param extGState the extended graphics state to be added
*/
public void put(COSName name, PDExtendedGraphicsState extGState)
{
put(COSName.EXT_G_STATE, name, extGState);
}
/**
* Sets the shading resource with the given name.
*
* @param name the name of the resource
* @param shading the shading to be added
*/
public void put(COSName name, PDShading shading)
{
put(COSName.SHADING, name, shading);
}
/**
* Sets the pattern resource with the given name.
*
* @param name the name of the resource
* @param pattern the pattern to be added
*/
public void put(COSName name, PDAbstractPattern pattern)
{
put(COSName.PATTERN, name, pattern);
}
/**
* Sets the property list resource with the given name.
*
* @param name the name of the resource
* @param properties the property list to be added
*/
public void put(COSName name, PDPropertyList properties)
{
put(COSName.PROPERTIES, name, properties);
}
/**
* Sets the XObject resource with the given name.
*
* @param name the name of the resource
* @param xobject the XObject to be added
*/
public void put(COSName name, PDXObject xobject)
{
put(COSName.XOBJECT, name, xobject);
}
/**
* Returns the resource cache associated with the Resources, or null if there is none.
*
* @return the resource cache associated with the resources.
*/
public ResourceCache getResourceCache()
{
return cache;
}
}