PdfType2Function.java

/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2025 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.itextpdf.kernel.pdf.function;

import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfNumber;

/**
 * This class represents Pdf type 2 function that defines an exponential
 * interpolation of one input value to n output values.
 *
 * <p>
 * For more info see ISO 32000-1, section 7.10.3 "Type 2 (Exponential Interpolation) Functions".
 */
public class PdfType2Function extends AbstractPdfFunction<PdfDictionary> {
    private double[] c0;

    private double[] c1;

    private double n;

    /**
     * Instantiates a new PdfType2Function instance based on passed PdfDictionary instance.
     *
     * @param dict the function dictionary
     */
    public PdfType2Function(PdfDictionary dict) {
        super(dict);

        final PdfNumber nObj = dict.getAsNumber(PdfName.N);
        if (nObj == null) {
            throw new PdfException(KernelExceptionMessageConstant.INVALID_TYPE_2_FUNCTION_N);
        }
        n = nObj.doubleValue();

        if ( super.getDomain().length < 2) {
            throw new PdfException(KernelExceptionMessageConstant.INVALID_TYPE_2_FUNCTION_DOMAIN);
        }

        if (n != Math.floor(n) && super.getDomain()[0] < 0) {
            throw new PdfException(KernelExceptionMessageConstant.INVALID_TYPE_2_FUNCTION_N_NOT_INTEGER);
        }
        if (n < 0 && super.clipInput(new double[] {0})[0] == 0) {
            throw new PdfException(KernelExceptionMessageConstant.INVALID_TYPE_2_FUNCTION_N_NEGATIVE);
        }

        final PdfArray c0Obj = dict.getAsArray(PdfName.C0);
        final PdfArray c1Obj = dict.getAsArray(PdfName.C1);
        final PdfArray rangeObj = dict.getAsArray(PdfName.Range);
        c0 = initializeCArray(c0Obj, c1Obj, rangeObj, 0);
        c1 = initializeCArray(c1Obj, c0Obj, rangeObj, 1);

        if (c0.length != c1.length || (super.getRange() != null && c0.length != super.getRange().length / 2)) {
            throw new PdfException(KernelExceptionMessageConstant.INVALID_TYPE_2_FUNCTION_OUTPUT_SIZE);
        }
    }

    public PdfType2Function(double[] domain, double[] range, double[] c0, double[] c1, double n) {
        super(new PdfDictionary(), PdfFunctionFactory.FUNCTION_TYPE_2, domain, range);
        setC0(c0);
        setC1(c1);
        setN(n);
    }

    public PdfType2Function(float[] domain, float[] range, float[] c0, float[] c1, double n) {
        this(convertFloatArrayToDoubleArray(domain), convertFloatArrayToDoubleArray(range),
                convertFloatArrayToDoubleArray(c0), convertFloatArrayToDoubleArray(c1), n);
    }

    @Override
    public double[] calculate(double[] input) {
        if (input == null || input.length != 1) {
            throw new PdfException(KernelExceptionMessageConstant.INVALID_INPUT_FOR_TYPE_2_FUNCTION);
        }
        double[] clipped = clipInput(input);
        final double x = clipped[0];
        final int outputSize = getOutputSize();
        final double[] output = new double[outputSize];
        for (int i = 0; i < outputSize; i++) {
            output[i] = c0[i] + Math.pow(x, n) * (c1[i] - c0[i]);
        }

        return clipOutput(output);
    }

    /**
     * Gets output size of function.
     *
     * <p>
     * If Range field is absent, the size of C0 array will be returned.
     *
     * @return output size of function
     */
    @Override
    public final int getOutputSize() {
        return getRange() == null ? c0.length : (getRange().length / 2);
    }

    /**
     * Gets values of C0 array.
     *
     * @return the values of C0 array
     */
    public final double[] getC0() {
        return c0;
    }

    /**
     * Sets values of C0 array.
     *
     * @param value  the values of C0 array
     */
    public final void setC0(double[] value) {
        getPdfObject().put(PdfName.C0, new PdfArray(value));
        c0 = value;
    }

    /**
     * Gets values of C1 array.
     *
     * @return the values of C1 array
     */
    public final double[] getC1() {
        return c1;
    }

    /**
     * Sets values of C1 array.
     *
     * @param value  the values of C1 array
     */
    public final void setC1(double[] value) {
        getPdfObject().put(PdfName.C1, new PdfArray(value));
        c1 = value;
    }

    /**
     * Gets value of N field.
     *
     * @return the value of N field
     */
    public final double getN() {
        return n;
    }

    /**
     * sets value of N field.
     *
     * @param value the value of N field
     */
    public final void setN(double value) {
        getPdfObject().put(PdfName.N, new PdfNumber(value));
        n = value;
    }

    private static double[] initializeCArray(PdfArray c, PdfArray otherC, PdfArray range, double defaultValue) {
        if (c != null) {
            return c.toDoubleArray();
        }

        double[] result;
        if (otherC == null) {
            if (range == null) {
                result = new double[1];
            } else {
                result = new double[range.size() / 2];
            }
        } else {
            result = new double[otherC.size()];
        }

        for (int i = 0; i < result.length; i++) {
            result[i] = defaultValue;
        }
        return result;
    }
}