# PDFunction.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 java.io.IOException;

    import org.apache.pdfbox.cos.COSArray;
    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.common.PDRange;
    import org.apache.pdfbox.pdmodel.common.PDStream;

    /**
     * This class represents a function in a PDF document.
     *
     * @author Ben Litchfield
     *
     */
    public abstract class PDFunction implements COSObjectable
    {

        private PDStream functionStream = null;
        private COSDictionary functionDictionary = null;
        private COSArray domain = null;
        private COSArray range = null;
        private int numberOfInputValues = -1;
        private int numberOfOutputValues = -1;

        /**
         * Constructor.
         *
         * @param function The function stream.
         *
         */
        public PDFunction( COSBase function )
        {
            if (function instanceof COSStream)
            {
                functionStream = new PDStream( (COSStream)function );
                functionStream.getCOSObject().setItem( COSName.TYPE, COSName.FUNCTION );
            }
            else if (function instanceof COSDictionary)
            {
                functionDictionary = (COSDictionary)function;
            }
        }

        /**
         * Returns the function type.
         *
         * Possible values are:
         *
         * 0 - Sampled function
         * 2 - Exponential interpolation function
         * 3 - Stitching function
         * 4 - PostScript calculator function
         *
         * @return the function type.
         */
        public abstract int getFunctionType();

        /**
         * Returns the stream.
         * @return The stream for this object.
         */
        @Override
        public final COSDictionary getCOSObject()
        {
            if (functionStream != null)
            {
                return functionStream.getCOSObject();
            }
            else
            {
                return functionDictionary;
            }
        }

        /**
         * Returns the underlying PDStream.
         * @return The stream.
         */
        protected PDStream getPDStream()
        {
            return functionStream;
        }

        /**
         * Create the correct PD Model function based on the COS base function.
         *
         * @param function The COS function dictionary.
         *
         * @return The PDModel Function object, never null.
         *
         * @throws IOException If we are unable to create the PDFunction object.
         */
        public static PDFunction create( COSBase function ) throws IOException
        {
            if (function == COSName.IDENTITY)
            {
                return new PDFunctionTypeIdentity(null);
            }

            COSBase base = function;
            if (function instanceof COSObject)
            {
                base = ((COSObject) function).getObject();
            }
            if (!(base instanceof COSDictionary))
            {
                throw new IOException("Error: Function must be a Dictionary, but is " +
                        (base == null ? "(null)" : base.getClass().getSimpleName()));
            }
            COSDictionary functionDictionary = (COSDictionary) base;
            int functionType = functionDictionary.getInt(COSName.FUNCTION_TYPE);
            switch (functionType)
            {
                case 0:
                    return new PDFunctionType0(functionDictionary);
                case 2:
                    return new PDFunctionType2(functionDictionary);
                case 3:
                    return new PDFunctionType3(functionDictionary);
                case 4:
                    return new PDFunctionType4(functionDictionary);
                default:
                    throw new IOException("Error: Unknown function type " + functionType);
            }
        }

        /**
         * This will get the number of output parameters that
         * have a range specified.  A range for output parameters
         * is optional so this may return zero for a function
         * that does have output parameters, this will simply return the
         * number that have the range specified.
         *
         * @return The number of output parameters that have a range
         * specified.
         */
        public int getNumberOfOutputParameters()
        {
            if (numberOfOutputValues == -1)
            {
                COSArray rangeValues = getRangeValues();
                if (rangeValues == null)
                {
                    numberOfOutputValues = 0;
                }
                else
                {
                    numberOfOutputValues = rangeValues.size() / 2;
                }
            }
            return numberOfOutputValues;
        }

        /**
         * This will get the range for a certain output parameters.  This is will never
         * return null.  If it is not present then the range 0 to 0 will
         * be returned.
         *
         * @param n The output parameter number to get the range for.
         *
         * @return The range for this component.
         */
        public PDRange getRangeForOutput(int n)
        {
            COSArray rangeValues = getRangeValues();
            return new PDRange( rangeValues, n );
        }

        /**
         * This will set the range values.
         *
         * @param rangeValues The new range values.
         */
        public void setRangeValues(COSArray rangeValues)
        {
            range = rangeValues;
            getCOSObject().setItem(COSName.RANGE, rangeValues);
        }

        /**
         * This will get the number of input parameters that
         * have a domain specified.
         *
         * @return The number of input parameters that have a domain
         * specified.
         */
        public int getNumberOfInputParameters()
        {
            if (numberOfInputValues == -1)
            {
                COSArray array = getDomainValues();
                numberOfInputValues = array.size() / 2;
            }
            return numberOfInputValues;
        }

        /**
         * This will get the range for a certain input parameter.  This is will never
         * return null.  If it is not present then the range 0 to 0 will
         * be returned.
         *
         * @param n The parameter number to get the domain for.
         *
         * @return The domain range for this component.
         */
        public PDRange getDomainForInput(int n)
        {
            COSArray domainValues = getDomainValues();
            return new PDRange( domainValues, n );
        }

        /**
         * This will set the domain values.
         *
         * @param domainValues The new domain values.
         */
        public void setDomainValues(COSArray domainValues)
        {
            domain = domainValues;
            getCOSObject().setItem(COSName.DOMAIN, domainValues);
        }

        /**
         * Evaluates the function at the given input.
         * ReturnValue = f(input)
         *
         * @param input The array of input values for the function.
         * In many cases will be an array of a single value, but not always.
         *
         * @return The of outputs the function returns based on those inputs.
         * In many cases will be an array of a single value, but not always.
         *
         * @throws IOException if something went wrong processing the function.
         */
        public abstract float[] eval(float[] input) throws IOException;

        /**
         * Returns all ranges for the output values as COSArray .
         * Required for type 0 and type 4 functions
         * @return the ranges array.
         */
        protected COSArray getRangeValues()
        {
            if (range == null)
            {
                range = getCOSObject().getCOSArray(COSName.RANGE);
            }
            return range;
        }

        /**
         * Returns all domains for the input values as COSArray.
         * Required for all function types.
         * @return the domains array.
         */
        private COSArray getDomainValues()
        {
            if (domain == null)
            {
                domain = getCOSObject().getCOSArray(COSName.DOMAIN);
            }
            return domain;
        }

        /**
         * Clip the given input values to the ranges.
         *
         * @param inputValues the input values
         * @return the clipped values
         */
        protected float[] clipToRange(float[] inputValues)
        {
            COSArray rangesArray = getRangeValues();
            float[] result;
            if (rangesArray != null && rangesArray.size() > 0)
            {
                float[] rangeValues = rangesArray.toFloatArray();
                int numberOfRanges = rangeValues.length/2;
                result = new float[numberOfRanges];
                for (int i=0; i<numberOfRanges; i++)
                {
                    int index = i << 1;
                    result[i] = clipToRange(inputValues[i], rangeValues[index], rangeValues[index + 1]);
                }
            }
            else
            {
                result = inputValues;
            }
            return result;
        }

        /**
         * Clip the given input value to the given range.
         *
         * @param x the input value
         * @param rangeMin the min value of the range
         * @param rangeMax the max value of the range

         * @return the clipped value
         */
        protected float clipToRange(float x, float rangeMin, float rangeMax)
        {
            if (x < rangeMin)
            {
                return rangeMin;
            }
            else if (x > rangeMax)
            {
                return rangeMax;
            }
            return x;
        }

        /**
         * For a given value of x, interpolate calculates the y value
         * on the line defined by the two points (xRangeMin , xRangeMax )
         * and (yRangeMin , yRangeMax ).
         *
         * @param x the to be interpolated value.
         * @param xRangeMin the min value of the x range
         * @param xRangeMax the max value of the x range
         * @param yRangeMin the min value of the y range
         * @param yRangeMax the max value of the y range
         * @return the interpolated y value
         */
        protected float interpolate(float x, float xRangeMin, float xRangeMax, float yRangeMin, float yRangeMax)
        {
            return yRangeMin + ((x - xRangeMin) * (yRangeMax - yRangeMin)/(xRangeMax - xRangeMin));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public String toString()
        {
            return "FunctionType" + getFunctionType();
        }
    }