PdfType0FunctionTest.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.pdf.PdfArray;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfNumber;
import com.itextpdf.kernel.pdf.PdfStream;
import com.itextpdf.test.AssertUtil;
import com.itextpdf.test.ExtendedITextTest;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Tag;

@Tag("IntegrationTest")
public class PdfType0FunctionTest extends ExtendedITextTest {

    protected static final double DELTA = 1e-12;

    @Test
    public void testEncoding() {
        int[][] encode = {
                {0, 1},
                {0, 10},
                {2, 7},
                {13, 21}
        };
        double[] x = {0, 0.3, 0.5, 0.9, 1};
        double[][] expected = {
                x,
                {0, 3, 5, 9, 10},
                {2, 3.5, 4.5, 6.5, 7},
                {13, 15.4, 17, 20.2, 21}
        };
        for (int i = 0; i < encode.length; ++i) {
            for (int j = 0; j < x.length; ++j) {
                double actual = PdfType0Function.encode(x[j], encode[i][0], encode[i][1]);

                Assertions.assertEquals(expected[i][j], actual, DELTA);
            }
        }
    }

    @Test
    public void testFloor() {
        double[][] normals = {
                {0.0, 0.0, 0.0},
                {0.5, 0.5, 0.6},
                {1.0, 1.0, 1.0}
        };
        int[] encode = {0, 1, 2, 7, 13, 21};
        int[][] expected = {
                {0, 2, 13},
                {0, 4, 17},
                {0, 6, 20}
        };
        for (int i = 0; i < normals.length; ++i) {
            int[] actual = PdfType0Function.getFloor(normals[i], encode);

            Assertions.assertArrayEquals(expected[i], actual);
        }
    }

    @Test
    public void testSamplePositionDim1() {
        for (int size = 2; size < 5; ++size) {
            int[] sizeArr = new int[] {size};
            for (int sample = 0; sample < size; ++sample) {
                int position = PdfType0Function.getSamplePosition(new int[] {sample}, sizeArr);

                Assertions.assertEquals(sample, position);
            }
        }
    }

    @Test
    public void testSamplePositionDim3() {
        int[][] size = {
                {2, 2, 2},
                {5, 5, 5},
                {8, 13, 21}
        };
        int[][][] samples = {
                {{0, 0, 0}, {1, 1, 1}, {0, 1, 0}, {1, 0, 1}},
                {{0, 0, 0}, {4, 4, 4}, {2, 3, 4}, {4, 3, 2}},
                {{0, 0, 0}, {7, 12, 20}, {0, 7, 20}, {6, 7, 8}}
        };
        int[][] expected = {
                {0, 7, 2, 5},
                {0, 124, 117, 69},
                {0, 2183, 2136, 894}
        };
        for (int i = 0; i < size.length; ++i) {
            for (int j = 0; j < samples[i].length; ++j) {
                int actual = PdfType0Function.getSamplePosition(samples[i][j], size[i]);
                Assertions.assertEquals(expected[i][j], actual);
            }
        }
    }

    @Test
    public void testFloorWeights() {
        double[][] normals = {
                {0.0, 0.0, 0.0},
                {0.3, 0.5, 0.7},
                {1.0, 1.0, 1.0}
        };
        int[] encode = {0, 1, 2, 7, 13, 21};
        double[][] expected = {
                {0, 0, 0},
                {0.3, 0.5, 0.6},
                {1, 1, 1}
        };

        for (int i = 0; i < normals.length; ++i) {
            double[] actual = PdfType0Function.getFloorWeights(normals[i], encode);

            Assertions.assertArrayEquals(expected[i], actual, DELTA);
        }
    }

    @Test
    public void testFloorWeight() {
        double[] normals = {0.0, 0.2, 0.4, 0.6, 0.8, 1.0};
        int[] encode = {0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 0, 34};
        double[] expected = {0.0, 0.2, 0.8, 0.0, 0.4, 1.0};

        for (int i = 0; i < normals.length; ++i) {
            double actual = PdfType0Function.getFloorWeight(normals[i], encode[2 * i], encode[2 * i + 1]);

            Assertions.assertEquals(expected[i], actual, DELTA);
        }
    }

    @Test
    public void testSpecialSweepMethod() {
        double[][] rhsVectors = {
                {1},
                {5, 5},
                {5, 6, 5},
                {6, 12, 18, 19}
        };
        double[][] expected = {
                {0, 0.25, 0},
                {0, 1, 1, 0},
                {0, 1, 1, 1, 0},
                {0, 1, 2, 3, 4, 0}
        };

        for (int i = 0; i < rhsVectors.length; ++i) {
            double[] actual = PdfType0Function.specialSweepMethod(rhsVectors[i]);
             Assertions.assertArrayEquals(expected[i], actual, DELTA);
        }
    }

    @Test
    public void testNoInputException() {
        AbstractPdfFunction<PdfStream> function = new PdfType0Function(new PdfStream());

        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_NOT_NULL_PARAMETERS, e.getMessage());
    }

    @Test
    public void testSimpleValidPdfFunction() {
        AbstractPdfFunction<PdfStream> function = generateSimplePdfFunction(new byte[] {0}, 1);
        AssertUtil.doesNotThrow(() -> function.calculate(new double[] {0}));
    }



    @Test
    public void testInvalidBitsPerSampleException() {
        PdfType0Function function = generateSimplePdfFunction(new byte[] {0}, 3);
        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_BITS_PER_SAMPLE_INVALID_VALUE, e.getMessage());
    }

    @Test
    public void testInvalidOrderException() {
        PdfType0Function function = generateSimplePdfFunction(new byte[] {0}, 1);
        function.setOrder(2);

        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_INVALID_ORDER, e.getMessage());
    }

    @Test
    public void testInvalidDomainException() {
        PdfType0Function function = generateSimplePdfFunction(new byte[] {0}, 1);
        function.setDomain(new double[] {0, 1, 1});

        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_INVALID_DOMAIN, e.getMessage());
    }

    @Test
    public void testInvalidRangeException() {
        PdfType0Function function = generateSimplePdfFunction(new byte[] {0}, 1);
        function.setRange(new double[] {0, 1, 1});

        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_INVALID_RANGE, e.getMessage());
    }

    @Test
    public void testInvalidSizeException() {
        PdfType0Function function = generateSimplePdfFunction(new byte[] {0}, 1);
        function.setSize(new int[] {2, 2});

        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_INVALID_SIZE, e.getMessage());
    }

    @Test
    public void testInvalidEncodeException() {
        PdfType0Function function = generateSimplePdfFunction(new byte[] {0}, 1);
        function.setEncode(new int[] {3, 4});

        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_INVALID_ENCODE, e.getMessage());
    }

    @Test
    public void testInvalidDecodeException() {
        PdfType0Function function = generateSimplePdfFunction(new byte[] {0}, 1);
        function.setDecode(new double[] {0});

        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_INVALID_DECODE, e.getMessage());
    }

    @Test
    public void testInvalidSamplesException() {
        PdfType0Function function = generateSimplePdfFunction(new byte[] {0x0f, 0x0f}, 4);
        function.setSize(new int[] {5});

        Exception e = Assertions
                .assertThrows(IllegalArgumentException.class, () -> function.calculate(new double[] {0}));
        Assertions.assertEquals(KernelExceptionMessageConstant.PDF_TYPE0_FUNCTION_INVALID_SAMPLES, e.getMessage());
    }

    private PdfType0Function generateSimplePdfFunction(byte[] samples, int bitsPerSample) {
        PdfStream stream = new PdfStream(samples);
        stream.put(PdfName.Domain, new PdfArray(new double[] {0, 1}));
        stream.put(PdfName.Range, new PdfArray(new double[] {0, 1}));
        stream.put(PdfName.Size, new PdfArray(new int[] {2}));
        stream.put(PdfName.BitsPerSample, new PdfNumber(bitsPerSample));

        return new PdfType0Function(stream);
    }
}