# PDFunctionType3.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.common.function;

    import org.apache.pdfbox.cos.COSArray;
    import org.apache.pdfbox.cos.COSBase;
    import org.apache.pdfbox.cos.COSName;
    import org.apache.pdfbox.pdmodel.common.PDRange;

    import java.io.IOException;

    /**
     * This class represents a Type 3 (stitching) function in a PDF document.
     *
     * @author Ben Litchfield
     */
    public class PDFunctionType3 extends PDFunction
    {
        private COSArray functions = null;
        private COSArray encode = null;
        private COSArray bounds = null;
        private PDFunction[] functionsArray = null;
        private float[] boundsValues = null;

        /**
         * Constructor.
         *
         * @param functionStream The function .
         */
        public PDFunctionType3(COSBase functionStream)
        {
            super( functionStream );
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public int getFunctionType()
        {
            return 3;
        }

        /**
        * {@inheritDoc}
        */
        @Override
        public float[] eval(float[] input) throws IOException
        {
            //This function is known as a "stitching" function. Based on the input, it decides which child function to call.
            // All functions in the array are 1-value-input functions
            //See PDF Reference section 3.9.3.
            PDFunction function = null;
            float x = input[0];
            PDRange domain = getDomainForInput(0);
            // clip input value to domain
            x = clipToRange(x, domain.getMin(), domain.getMax());

            if (functionsArray == null)
            {
                COSArray ar = getFunctions();
                functionsArray = new PDFunction[ar.size()];
                for (int i = 0; i < ar.size(); ++i)
                {
                    functionsArray[i] = PDFunction.create(ar.getObject(i));
                }
            }

            if (functionsArray.length == 1)
            {
                // This doesn't make sense but it may happen ...
                function = functionsArray[0];
                PDRange encRange = getEncodeForParameter(0);
                x = interpolate(x, domain.getMin(), domain.getMax(), encRange.getMin(), encRange.getMax());
            }
            else
            {
                if (boundsValues == null)
                {
                    boundsValues = getBounds().toFloatArray();
                }
                int boundsSize = boundsValues.length;
                // create a combined array containing the domain and the bounds values
                // domain.min, bounds[0], bounds[1], ...., bounds[boundsSize-1], domain.max
                float[] partitionValues = new float[boundsSize+2];
                int partitionValuesSize = partitionValues.length;
                partitionValues[0] = domain.getMin();
                partitionValues[partitionValuesSize-1] = domain.getMax();
                System.arraycopy(boundsValues, 0, partitionValues, 1, boundsSize);
                // find the partition
                for (int i=0; i < partitionValuesSize-1; i++)
                {
                    if ( x >= partitionValues[i] &&
                            (x < partitionValues[i+1] || (i == partitionValuesSize - 2 && Float.compare(x,partitionValues[i+1]) == 0)))
                    {
                        function = functionsArray[i];
                        PDRange encRange = getEncodeForParameter(i);
                        x = interpolate(x, partitionValues[i], partitionValues[i+1], encRange.getMin(), encRange.getMax());
                        break;
                    }
                }
                if (function == null)
                {
                    throw new IOException("partition not found in type 3 function");
                }
            }
            float[] functionValues = new float[]{x};
            // calculate the output values using the chosen function
            float[] functionResult = function.eval(functionValues);
            // clip to range if available
            return clipToRange(functionResult);
        }

        /**
         * Returns all functions values as COSArray.
         *
         * @return the functions array.
         */
        public COSArray getFunctions()
        {
            if (functions == null)
            {
                functions = getCOSObject().getCOSArray(COSName.FUNCTIONS);
            }
            return functions;
        }

        /**
         * Returns all bounds values as COSArray.
         *
         * @return the bounds array.
         */
        public COSArray getBounds()
        {
            if (bounds == null)
            {
                bounds = getCOSObject().getCOSArray(COSName.BOUNDS);
            }
            return bounds;
        }

        /**
         * Returns all encode values as COSArray.
         *
         * @return the encode array.
         */
        public COSArray getEncode()
        {
            if (encode == null)
            {
                encode = getCOSObject().getCOSArray(COSName.ENCODE);
            }
            return encode;
        }

        /**
         * Get the encode for the input parameter.
         *
         * @param n The function parameter number.
         *
         * @return The encode parameter range or null if none is set.
         */
        private PDRange getEncodeForParameter(int n)
        {
            COSArray encodeValues = getEncode();
            return new PDRange( encodeValues, n );
        }

    }