/* * 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.io.SeekableInputConstrainedWrapper; import org.icepdf.core.pobjects.graphics.ExtGState; import org.icepdf.core.pobjects.graphics.GraphicsState; import org.icepdf.core.pobjects.graphics.Shapes; import org.icepdf.core.util.Library; import org.icepdf.core.util.content.ContentParser; import org.icepdf.core.util.content.ContentParserFactory; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Form XObject class. Not currently part of the public api. * <br> * 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()); public static final Name TYPE_VALUE = new Name("XObject"); public static final Name SUB_TYPE_VALUE = new Name("Form"); public static final Name GROUP_KEY = new Name("Group"); public static final Name I_KEY = new Name("I"); public static final Name K_KEY = new Name("K"); public static final Name MATRIX_KEY = new Name("Matrix"); public static final Name BBOX_KEY = new Name("BBox"); public static final Name RESOURCES_KEY = new Name("Resources"); private AffineTransform matrix = new AffineTransform(); private Rectangle2D bbox; private Shapes shapes; // Graphics state object to be used by content parser private GraphicsState graphicsState; private ExtGState extGState; private Resources resources; private Resources parentResource; // transparency grouping data private boolean transparencyGroup; private boolean isolated; private boolean knockOut; private boolean shading; 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, HashMap h, SeekableInputConstrainedWrapper streamInputWrapper) { super(l, h, streamInputWrapper); // check for grouping flags so we can do special handling during the // xform content stream parsing. HashMap group = library.getDictionary(entries, GROUP_KEY); if (group != null) { transparencyGroup = true; isolated = library.getBoolean(group, I_KEY); knockOut = library.getBoolean(group, K_KEY); } } public HashMap getGroup() { return library.getDictionary(entries, GROUP_KEY); } @SuppressWarnings("unchecked") public void setAppearance(Shapes shapes, AffineTransform matrix, Rectangle2D bbox) { inited = false; this.shapes = shapes; this.matrix = matrix; this.bbox = bbox; entries.put(Form.BBOX_KEY, PRectangle.getPRectangleVector(bbox)); entries.put(Form.MATRIX_KEY, matrix); } /** * 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; this.extGState = graphicsState.getExtGState(); } } /** * Gets the associated graphic state instance for this form. * * @return external graphic state, can be null. */ public GraphicsState getGraphicsState() { return graphicsState; } /** * Gets the extended graphics state for the form at the time of creation. This contains any masking and blending * data that might bet over written during the forms parsing. * * @return extended graphic state at the time of creation. */ public ExtGState getExtGState() { return extGState; } /** * 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(List v) { float f[] = new float[6]; for (int i = 0; i < 6; i++) { f[i] = ((Number) v.get(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; } Object v = library.getObject(entries, MATRIX_KEY); if (v != null && v instanceof List) { matrix = getAffineTransform((List) v); } else if (v != null && v instanceof AffineTransform) { matrix = (AffineTransform) v; } bbox = library.getRectangle(entries, BBOX_KEY); // try and find the form's resources dictionary. Resources leafResources = library.getResources(entries, RESOURCES_KEY); // apply parent resource, if the current resources is null if (leafResources != null) { resources = leafResources; } else { leafResources = parentResource; } // Build a new content parser for the content streams and apply the // content stream of the calling content stream. ContentParser cp = ContentParserFactory.getInstance() .getContentParser(library, leafResources); cp.setGraphicsState(graphicsState); byte[] in = getDecodedStreamBytes(); if (in != null) { try { if (logger.isLoggable(Level.FINER)) { logger.finer("Parsing form " + getPObjectReference()); } shapes = cp.parse(new byte[][]{in}, null).getShapes(); } 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); } } inited = true; } public Resources getResources() { Resources leafResources = library.getResources(entries, RESOURCES_KEY); if (leafResources == null) { leafResources = new Resources(library, new HashMap()); } return leafResources; } @SuppressWarnings("unchecked") public void setResources(Resources resources) { entries.put(RESOURCES_KEY, resources.getEntries()); } /** * 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; } public boolean isShading() { return shading; } public void setShading(boolean shading) { this.shading = shading; } }