PdfPrimitivesTest.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;

import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.source.ByteArrayOutputStream;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.TestUtil;
import com.itextpdf.test.annotations.LogMessage;
import com.itextpdf.test.annotations.LogMessages;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;

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

    static final String destinationFolder = TestUtil.getOutputPath() + "/kernel/pdf/PdfPrimitivesTest/";
    static final PdfName TestArray = new PdfName("TestArray");
    static final int DefaultArraySize = 64;
    static final int PageCount = 1000;

    public static class RandomString {

        private static final char[] symbols;
        private final Random random = new Random();
        private final char[] buf;

        static {
            StringBuilder tmp = new StringBuilder();
            for (char ch = 'A'; ch <= 'Z'; ++ch)
                tmp.append(ch);
            for (char ch = 'a'; ch <= 'z'; ++ch)
                tmp.append(ch);
            for (char ch = '0'; ch <= '9'; ++ch)
                tmp.append(ch);
            symbols = tmp.toString().toCharArray();
        }

        public RandomString(int length) {
            if (length < 1)
                throw new IllegalArgumentException("length < 1: " + length);
            buf = new char[length];
        }

        public String nextString() {
            for (int idx = 0; idx < buf.length; ++idx)
                buf[idx] = symbols[random.nextInt(symbols.length)];
            return new String(buf);
        }
    }

    @BeforeAll
    public static void beforeClass() {
        createDestinationFolder(destinationFolder);
    }

    @AfterAll
    public static void afterClass() {
        CompareTool.cleanup(destinationFolder);
    }
    
    @Test
    public void primitivesFloatNumberTest() throws IOException {
        String filename = "primitivesFloatNumberTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            PdfArray array = generatePdfArrayWithFloatNumbers(null, false);
            page.getPdfObject().put(TestArray, array);
            array.flush();
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void primitivesIntNumberTest() throws IOException {
        String filename = "primitivesIntNumberTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            PdfArray array = generatePdfArrayWithIntNumbers(null, false);
            page.getPdfObject().put(TestArray, array);
            array.flush();
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void primitivesNameTest() throws IOException {
        String filename = "primitivesNameTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            PdfArray array = generatePdfArrayWithNames(null, false);
            page.getPdfObject().put(TestArray, array);
            array.flush();
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void primitivesStringTest() throws IOException {
        String filename = "primitivesStringTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            PdfArray array = generatePdfArrayWithStrings(null, false);
            page.getPdfObject().put(TestArray, array);
            array.flush();
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void primitivesBooleanTest() throws IOException {
        String filename = "primitivesBooleanTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            page.getPdfObject().put(TestArray, generatePdfArrayWithBooleans(null, false));
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void primitivesFloatNumberIndirectTest() throws IOException {
        String filename = "primitivesFloatNumberIndirectTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            page.getPdfObject().put(TestArray, generatePdfArrayWithFloatNumbers(pdfDoc, true));
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void primitivesIntNumberIndirectTest() throws IOException {
        String filename = "primitivesIntNumberIndirectTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            page.getPdfObject().put(TestArray, generatePdfArrayWithIntNumbers(pdfDoc, true));
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void primitivesStringIndirectTest() throws IOException {
        String filename = "primitivesStringIndirectTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            page.getPdfObject().put(TestArray, generatePdfArrayWithStrings(pdfDoc, true));
            page.flush();
        }
        pdfDoc.close();
    }



    @Test
    public void primitivesNameIndirectTest() throws IOException {
        String filename = "primitivesNameIndirectTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            page.getPdfObject().put(TestArray, generatePdfArrayWithNames(pdfDoc, true));
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void primitivesBooleanIndirectTest() throws IOException {
        String filename = "primitivesBooleanIndirectTest.pdf";
        PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + filename));
        for (int i = 0; i < PageCount; i++) {
            PdfPage page = pdfDoc.addNewPage();
            page.getPdfObject().put(TestArray, generatePdfArrayWithBooleans(pdfDoc, true));
            page.flush();
        }
        pdfDoc.close();
    }

    @Test
    public void pdfNamesTest() {
        RandomString rnd = new RandomString(16);
        for (int i = 0; i < 10000000; i++) {
            new PdfName(rnd.nextString());
        }
    }

    @Test
    @LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.DIRECTONLY_OBJECT_CANNOT_BE_INDIRECT)})
    public void makeIndirectDirectOnlyPdfBoolean() {
        PdfDocument pdfDoc = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
        PdfBoolean t = PdfBoolean.valueOf(true);
        t.makeIndirect(pdfDoc);
    }

    @Test
    public void equalStrings() {
        PdfString a = (PdfString) new PdfString("abcd").makeIndirect(new PdfDocument(new PdfWriter(new ByteArrayOutputStream())));
        PdfString b = new PdfString("abcd".getBytes(StandardCharsets.US_ASCII));
        Assertions.assertTrue(a.equals(b));

        PdfString c = new PdfString("abcd", "UTF-8");
        Assertions.assertFalse(c.equals(a));
    }

    @Test
    public void equalNumbers() {
        try (PdfDocument document = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) {
            // add a page to avoid exception throwing on close
            document.addNewPage();

            PdfNumber num1 = (PdfNumber) new PdfNumber(1).makeIndirect(document);
            PdfNumber num2 = new PdfNumber(2);

            Assertions.assertFalse(num1.equals(num2));

            int hashCode = num1.hashCode();
            num1.increment();

            Assertions.assertTrue(num1.equals(num2));
            Assertions.assertNotEquals(hashCode, num1.hashCode());
        }

        PdfNumber a = new PdfNumber(1);
        PdfNumber aContent = new PdfNumber(a.getInternalContent());
        PdfNumber b = new PdfNumber(2);
        PdfNumber bContent = new PdfNumber(b.getInternalContent());

        Assertions.assertTrue(a.equals(aContent));
        Assertions.assertEquals(a.hashCode(), aContent.hashCode());
        Assertions.assertTrue(b.equals(bContent));
        Assertions.assertEquals(b.hashCode(), bContent.hashCode());
        Assertions.assertFalse(aContent.equals(bContent));
        Assertions.assertNotEquals(aContent.hashCode(), bContent.hashCode());

        aContent.increment();

        Assertions.assertFalse(a.equals(aContent));
        Assertions.assertNotEquals(a.hashCode(), aContent.hashCode());
        Assertions.assertTrue(aContent.equals(bContent));
        Assertions.assertEquals(aContent.hashCode(), bContent.hashCode());
    }

    @Test
    public void equalNames() {
        PdfName a = (PdfName) new PdfName("abcd").makeIndirect(new PdfDocument(new PdfWriter(new ByteArrayOutputStream())));
        PdfName b = new PdfName("abcd");
        Assertions.assertTrue(a.equals(b));
    }

    @Test
    public void equalBoolean() {
        PdfBoolean f = (PdfBoolean) new PdfBoolean(false).makeIndirect(new PdfDocument(new PdfWriter(new ByteArrayOutputStream())));
        PdfBoolean t = new PdfBoolean(true);
        Assertions.assertFalse(f.equals(t));
        Assertions.assertTrue(f.equals(PdfBoolean.FALSE));
        Assertions.assertTrue(t.equals(PdfBoolean.TRUE));
    }

    @Test
    public void equalNulls() {
        PdfNull a = (PdfNull) new PdfNull().makeIndirect(new PdfDocument(new PdfWriter(new ByteArrayOutputStream())));;
        Assertions.assertTrue(a.equals(PdfNull.PDF_NULL));
    }

    @Test
    public void equalLiterals() {
        PdfLiteral a = new PdfLiteral("abcd");
        PdfLiteral b = new PdfLiteral("abcd".getBytes(StandardCharsets.US_ASCII));
        Assertions.assertTrue(a.equals(b));
    }

    private PdfArray generatePdfArrayWithFloatNumbers(PdfDocument doc, boolean indirects) {
        PdfArray array = (PdfArray) new PdfArray().makeIndirect(doc);
        Random rnd = new Random();
        for (int i = 0; i < DefaultArraySize; i++) {
            PdfNumber num = new PdfNumber(rnd.nextFloat());
            if (indirects)
                num.makeIndirect(doc);
            array.add(num);
        }
        return array;
    }

    private PdfArray generatePdfArrayWithIntNumbers(PdfDocument doc, boolean indirects) {
        PdfArray array = (PdfArray) new PdfArray().makeIndirect(doc);
        Random rnd = new Random();
        for (int i = 0; i < DefaultArraySize; i++) {
            array.add(new PdfNumber(rnd.nextInt()).makeIndirect(indirects ? doc : null));
        }
        return array;
    }

    private PdfArray generatePdfArrayWithStrings(PdfDocument doc, boolean indirects) {
        PdfArray array = (PdfArray) new PdfArray().makeIndirect(doc);
        RandomString rnd = new RandomString(16);
        for (int i = 0; i < DefaultArraySize; i++) {
            array.add(new PdfString(rnd.nextString()).makeIndirect(indirects ? doc : null));
        }
        return array;
    }

    private PdfArray generatePdfArrayWithNames(PdfDocument doc, boolean indirects) {
        PdfArray array = (PdfArray) new PdfArray().makeIndirect(doc);
        RandomString rnd = new RandomString(6);
        for (int i = 0; i < DefaultArraySize; i++) {
            array.add(new PdfName(rnd.nextString()).makeIndirect(indirects ? doc : null));
        }
        return array;
    }

    private PdfArray generatePdfArrayWithBooleans(PdfDocument doc, boolean indirects) {
        PdfArray array = (PdfArray) new PdfArray().makeIndirect(doc);
        Random rnd = new Random();
        for (int i = 0; i < DefaultArraySize; i++) {
            array.add(new PdfBoolean(rnd.nextBoolean()).makeIndirect(indirects ? doc : null));
        }
        return array;
    }
}