/* * 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 <COSName,SoftReference<PDFont>> 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. * @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<PDFont> 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. */ 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. */ 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. * @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. * @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. */ 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. * @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) { if (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. */ public Iterable<COSName> getColorSpaceNames() { return getNames(COSName.COLORSPACE); } /** * Returns the names of the XObject resources, if any. */ public Iterable<COSName> getXObjectNames() { return getNames(COSName.XOBJECT); } /** * Returns the names of the font resources, if any. */ public Iterable<COSName> getFontNames() { return getNames(COSName.FONT); } /** * Returns the names of the property list resources, if any. */ public Iterable<COSName> getPropertiesNames() { return getNames(COSName.PROPERTIES); } /** * Returns the names of the shading resources, if any. */ public Iterable<COSName> getShadingNames() { return getNames(COSName.SHADING); } /** * Returns the names of the pattern resources, if any. */ public Iterable<COSName> getPatternNames() { return getNames(COSName.PATTERN); } /** * Returns the names of the extended graphics state resources, if any. */ public Iterable<COSName> getExtGStateNames() { return getNames(COSName.EXT_G_STATE); } /** * Returns the resource names of the given kind. */ private Iterable<COSName> 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. */ public ResourceCache getResourceCache() { return cache; } }