/*
* Copyright 2006-2012 ICEsoft Technologies Inc.
*
* 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.io.SeekableInputConstrainedWrapper;
import org.icepdf.core.pobjects.graphics.GraphicsState;
import org.icepdf.core.pobjects.graphics.Shapes;
import org.icepdf.core.util.ContentParser;
import org.icepdf.core.util.Library;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Form XObject class. Not currently part of the public api.
* <p/>
* Forms are grouped into the 'Resource' category and can be shared. As a result we need to make sure
* that the init method are synchronized as they can be accessed by different page loading threads.
*
* @since 1.0
*/
public class Form extends Stream {
private static final Logger logger =
Logger.getLogger(Form.class.toString());
private AffineTransform matrix = new AffineTransform();
private Rectangle2D bbox;
private Shapes shapes;
// Graphics state object to be used by content parser
private GraphicsState graphicsState;
private Resources resources;
private Resources parentResource;
// transparency grouping data
private boolean transparencyGroup;
private boolean isolated;
private boolean knockOut;
private boolean inited = false;
/**
* Creates a new instance of the xObject.
* @param l document library
* @param h xObject dictionary entries.
* @param streamInputWrapper content stream of image or post script commands.
*/
public Form(Library l, Hashtable h, SeekableInputConstrainedWrapper streamInputWrapper) {
super(l, h, streamInputWrapper);
// check for grouping flags so we can do special handling during the
// xform content stream parsing.
Hashtable group = library.getDictionary(entries, "Group");
if (group != null) {
transparencyGroup = true;
isolated = library.getBoolean(group, "I");
knockOut = library.getBoolean(group, "K");
}
}
/**
* When a Page refers to a Form, it calls this to cleanup and get rid of,
* or reduce the memory footprint of, the refered objects
*/
public void dispose(boolean cache) {
if (shapes != null){
shapes.dispose();
}
if (cache){
library.removeObject(this.getPObjectReference());
}
// get rid of the resources.
disposeResources(cache);
}
/**
* Disposes the resources associated with this Form object but leaves the
* shapes associated with a content stream. This method should only be
* called when the Xobject will be orphaned and dispose cannot be called
* via normal object chaining. XObject can be orphaned when called from
* a content stream of an Xobject.
*
* @param cache true indicates the cache should be used.
*/
public void disposeResources(boolean cache){
if (resources != null) {
resources.dispose(cache, this);
}
if (parentResource != null) {
// remove parent reference to parent
parentResource = null;
}
inited = false;
graphicsState = null;
// clean up the super stream
super.dispose(cache);
}
/**
* When a ContentParser refers to a Form, it calls this to signal that it
* is done with the Form itself, while the generated Shapes are still
* in use.
*/
public void completed() {
// null the reference, the shapes are refered to in the
// parent page and will be disposed of later if needed.
// shapes = null;
if (resources != null) {
resources.removeReference(this);
resources = null;
}
parentResource = null;
graphicsState = null;
inited = false;
}
/**
* Sets the GraphicsState which should be used by the content parser when
* parsing the Forms content stream. The GraphicsState should be set
* before init() is called, or it will have not effect on the rendered
* content.
*
* @param graphicsState current graphic state
*/
public void setGraphicsState(GraphicsState graphicsState) {
if (graphicsState != null) {
this.graphicsState = graphicsState;
}
}
/**
* Utility method for parsing a vector of affinetranform values to an
* affine transform.
*
* @param v vectory containing affine transform values.
* @return affine tansform based on v
*/
private static AffineTransform getAffineTransform(Vector v) {
float f[] = new float[6];
for (int i = 0; i < 6; i++) {
f[i] = ((Number) v.elementAt(i)).floatValue();
}
return new AffineTransform(f);
}
/**
* As of the PDF 1.2 specification, a resource entry is not required for
* a XObject and thus it needs to point to the parent resource to enable
* to correctly load the content stream.
*
* @param parentResource parent objects resourse when available.
*/
public void setParentResources(Resources parentResource) {
this.parentResource = parentResource;
}
/**
*
*/
public synchronized void init() {
if (inited) {
return;
}
Vector v = (Vector) library.getObject(entries, "Matrix");
if (v != null) {
matrix = getAffineTransform(v);
}
bbox = library.getRectangle(entries, "BBox");
// try and find the form's resources dictionary.
Resources leafResources = library.getResources(entries, "Resources");
// apply parent resource, if the current resources is null
if (leafResources != null) {
resources = leafResources;
resources.addReference(this);
} else {
leafResources = parentResource;
}
// Build a new content parser for the content streams and apply the
// content stream of the calling content stream.
ContentParser cp = new ContentParser(library, leafResources);
cp.setGraphicsState(graphicsState);
InputStream in = getInputStreamForDecodedStreamBytes();
if (in != null) {
try {
shapes = cp.parse(in);
}
catch (Throwable e) {
// reset shapes vector, we don't want to mess up the paint stack
shapes = new Shapes();
logger.log(Level.FINE, "Error parsing Form content stream.", e);
}
finally {
try {
in.close();
}
catch (IOException e) {
// intentionally left blank.
}
}
}
inited = true;
}
/**
* Gets the shapes that where parsed from the content stream.
*
* @return shapes object for xObject.
*/
public Shapes getShapes() {
return shapes;
}
/**
* Gets the bounding box for the xObject.
*
* @return rectangle in PDF coordinate space representing xObject bounds.
*/
public Rectangle2D getBBox() {
return bbox;
}
/**
* Gets the optional matrix which describes how to convert the coordinate
* system in xObject space to the parent coordinates space.
*
* @return affine transform representing the xObject's pdf to xObject space
* transform.
*/
public AffineTransform getMatrix() {
return matrix;
}
/**
* If the xObject has a transparency group flag.
*
* @return true if a transparency group exists, false otherwise.
*/
public boolean isTransparencyGroup() {
return transparencyGroup;
}
/**
* Only present if a transparency group is present. Isolated groups are
* composed on a fully transparent back drop rather then the groups.
*
* @return true if the transparency group is isolated.
*/
public boolean isIsolated() {
return isolated;
}
/**
* Only present if a transparency group is present. Knockout groups individual
* elements composed with the groups initial back drop rather then the stack.
*
* @return true if the transparency group is a knockout.
*/
public boolean isKnockOut() {
return knockOut;
}
}