# PDFormXObject.java

    /*
     * 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.graphics.form;

    import java.awt.geom.AffineTransform;
    import java.io.IOException;
    import java.io.InputStream;
    import org.apache.pdfbox.contentstream.PDContentStream;
    import org.apache.pdfbox.cos.COSArray;
    import org.apache.pdfbox.cos.COSDictionary;
    import org.apache.pdfbox.cos.COSFloat;
    import org.apache.pdfbox.cos.COSName;
    import org.apache.pdfbox.cos.COSStream;
    import org.apache.pdfbox.io.RandomAccessRead;
    import org.apache.pdfbox.pdmodel.PDDocument;
    import org.apache.pdfbox.pdmodel.PDResources;
    import org.apache.pdfbox.pdmodel.ResourceCache;
    import org.apache.pdfbox.pdmodel.common.PDRectangle;
    import org.apache.pdfbox.pdmodel.common.PDStream;
    import org.apache.pdfbox.pdmodel.documentinterchange.markedcontent.PDPropertyList;
    import org.apache.pdfbox.pdmodel.graphics.PDXObject;
    import org.apache.pdfbox.util.Matrix;

    /*
    TODO There are further Form XObjects to implement:

    + PDFormXObject
    |- PDReferenceXObject
    |- PDGroupXObject
       |- PDTransparencyXObject

    See PDF 32000 p111

    When doing this all methods on PDFormXObject should probably be made
    final and all fields private.
    */

    /**
     * A Form XObject.
     *
     * @author Ben Litchfield
     */
    public class PDFormXObject extends PDXObject implements PDContentStream
    {
        private PDTransparencyGroupAttributes group;
        private final ResourceCache cache;

        /**
         * Creates a Form XObject for reading.
         * @param stream The XObject stream
         */
        public PDFormXObject(PDStream stream)
        {
            super(stream, COSName.FORM);
            cache = null;
        }

        /**
         * Creates a Form XObject for reading.
         * @param stream The XObject stream
         */
        public PDFormXObject(COSStream stream)
        {
            super(stream, COSName.FORM);
            cache = null;
        }

        /**
         * Creates a Form XObject for reading.
         * @param stream The XObject stream
         */
        public PDFormXObject(COSStream stream, ResourceCache cache)
        {
            super(stream, COSName.FORM);
            this.cache = cache;
        }

        /**
         * Creates a Form Image XObject for writing, in the given document.
         * @param document The current document
         */
        public PDFormXObject(PDDocument document)
        {
            super(document, COSName.FORM);
            cache = null;
        }

        /**
         * This will get the form type, currently 1 is the only form type.
         * @return The form type.
         */
        public int getFormType()
        {
            return getCOSObject().getInt(COSName.FORMTYPE, 1);
        }

        /**
         * Set the form type.
         * @param formType The new form type.
         */
        public void setFormType(int formType)
        {
            getCOSObject().setInt(COSName.FORMTYPE, formType);
        }

        /**
         * Returns the group attributes dictionary.
         *
         * @return the group attributes dictionary
         */
        public PDTransparencyGroupAttributes getGroup()
        {
            if( group == null )
            {
                COSDictionary dic = getCOSObject().getCOSDictionary(COSName.GROUP);
                if( dic != null )
                {
                    group = new PDTransparencyGroupAttributes(dic);
                }
            }
            return group;
        }

        public PDStream getContentStream()
        {
            return new PDStream(getCOSObject());
        }

        @Override
        public InputStream getContents() throws IOException
        {
            return getCOSObject().createInputStream();
        }

        @Override
        public RandomAccessRead getContentsForRandomAccess() throws IOException
        {
            return getCOSObject().createView();
        }
        /**
         * This will get the resources for this Form XObject.
         * This will return null if no resources are available.
         *
         * @return The resources for this Form XObject.
         */
        @Override
        public PDResources getResources()
        {
            COSDictionary resources = getCOSObject().getCOSDictionary(COSName.RESOURCES);
            if (resources != null)
            {
                return new PDResources(resources, cache);
            }
            if (getCOSObject().containsKey(COSName.RESOURCES))
            {
                // PDFBOX-4372 if the resource key exists but has nothing, return empty resources,
                // to avoid a self-reference (xobject form Fm0 contains "/Fm0 Do")
                // See also the mention of PDFBOX-1359 in PDFStreamEngine
                return new PDResources();
            }
            return null;
        }

        /**
         * This will set the resources for this page.
         * @param resources The new resources for this page.
         */
        public void setResources(PDResources resources)
        {
            getCOSObject().setItem(COSName.RESOURCES, resources);
        }

        /**
         * An array of four numbers in the form coordinate system (see below),
         * giving the coordinates of the left, bottom, right, and top edges, respectively,
         * of the form XObject's bounding box.
         * These boundaries are used to clip the form XObject and to determine its size for caching.
         * @return The BBox of the form.
         */
        @Override
        public PDRectangle getBBox()
        {
            COSArray array = getCOSObject().getCOSArray(COSName.BBOX);
            return array != null ? new PDRectangle(array) : null;
        }

        /**
         * This will set the BBox (bounding box) for this form.
         * @param bbox The new BBox for this form.
         */
        public void setBBox(PDRectangle bbox)
        {
            if (bbox == null)
            {
                getCOSObject().removeItem(COSName.BBOX);
            }
            else
            {
                getCOSObject().setItem(COSName.BBOX, bbox.getCOSArray());
            }
        }

        /**
         * This will get the optional matrix of an XObjectForm. It maps the form space to user space.
         * @return the form matrix if available, or the identity matrix.
         */
        @Override
        public Matrix getMatrix()
        {
            return Matrix.createMatrix(getCOSObject().getDictionaryObject(COSName.MATRIX));
        }

        /**
         * Sets the optional Matrix entry for the form XObject.
         * @param transform the transformation matrix
         */
        public void setMatrix(AffineTransform transform)
        {
            COSArray matrix = new COSArray();
            double[] values = new double[6];
            transform.getMatrix(values);
            for (double v : values)
            {
                matrix.add(new COSFloat((float) v));
            }
            getCOSObject().setItem(COSName.MATRIX, matrix);
        }

        /**
         * This will get the key of this XObjectForm in the structural parent tree. Required if the form
         * XObject contains marked-content sequences that are structural content items.
         *
         * @return the integer key of the XObjectForm's entry in the structural parent tree or -1 if
         * there isn't any.
         */
        public int getStructParents()
        {
            return getCOSObject().getInt(COSName.STRUCT_PARENTS);
        }

        /**
         * This will set the key for this XObjectForm in the structural parent tree.
         * @param structParent The new key for this XObjectForm.
         */
        public void setStructParents(int structParent)
        {
            getCOSObject().setInt(COSName.STRUCT_PARENTS, structParent);
        }

        /**
         * This will get the optional content group or optional content membership dictionary.
         *
         * @return The optional content group or optional content membership dictionary or null if there
         * is none.
         */
        public PDPropertyList getOptionalContent()
        {
            COSDictionary optionalContent = getCOSObject().getCOSDictionary(COSName.OC);
            return optionalContent != null ? PDPropertyList.create(optionalContent) : null;
        }

        /**
         * Sets the optional content group or optional content membership dictionary.
         *
         * @param oc The optional content group or optional content membership dictionary.
         */
        public void setOptionalContent(PDPropertyList oc)
        {
            getCOSObject().setItem(COSName.OC, oc);
        }
    }