PdfActionTest.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.commons.utils.FileUtil;
import com.itextpdf.commons.utils.MessageFormatUtil;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.action.PdfAction;
import com.itextpdf.kernel.pdf.action.PdfActionOcgState;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.annot.PdfLineAnnotation;
import com.itextpdf.kernel.pdf.filespec.PdfStringFS;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.TestUtil;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
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 PdfActionTest extends ExtendedITextTest {

    public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/pdf/PdfActionTest/";
    public static final String destinationFolder = TestUtil.getOutputPath() + "/kernel/pdf/PdfActionTest/";

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

    @AfterAll
    public static void afterClass() {
        CompareTool.cleanup(destinationFolder);
    }
    
    @Test
    public void actionTest01() throws Exception {
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + "actionTest01.pdf"), true);

        document.getCatalog().setOpenAction(PdfAction.createURI("http://itextpdf.com/"));

        document.close();

        System.out.println(MessageFormatUtil.format("Please open document {0} and make sure that you're automatically redirected to {1} site.", destinationFolder + "actionTest01.pdf", "http://itextpdf.com"));
    }

    @Test
    public void actionTest02() throws Exception {
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + "actionTest02.pdf"), false);

        document.getPage(2).setAdditionalAction(PdfName.O, PdfAction.createURI("http://itextpdf.com/"));

        document.close();

        System.out.println(MessageFormatUtil.format("Please open document {0} at page 2 and make sure that you're automatically redirected to {1} site.", destinationFolder + "actionTest02.pdf", "http://itextpdf.com"));
    }

    @Test
    public void actionTest03() throws Exception {
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + "actionTest03.pdf"), true);
        String uri = "http://itextpdf.com/";

        document.getCatalog().setOpenAction(PdfAction.createURI(new URI(uri)));
        Assertions.assertEquals(new PdfString(uri),
                document.getCatalog().getPdfObject().getAsDictionary(PdfName.OpenAction).get(PdfName.URI));
        document.close();

        System.out.println(MessageFormatUtil.format("Please open document {0} and make sure that you're automatically redirected to {1} site.", destinationFolder + "actionTest01.pdf", "http://itextpdf.com"));
    }

    @Test
    public void soundActionTest() throws Exception {
        String fileName = "soundActionTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        InputStream is = FileUtil.getInputStreamForFile(sourceFolder + "sample.aif");
        PdfStream sound1 = new PdfStream(document, is);
        sound1.put(PdfName.R, new PdfNumber(32117));
        sound1.put(PdfName.E, PdfName.Signed);
        sound1.put(PdfName.B, new PdfNumber(16));
        sound1.put(PdfName.C, new PdfNumber(1));

        document.getPage(2).setAdditionalAction(PdfName.O, PdfAction.createSound(sound1));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void soundActionWithRepeatFlagTest() throws Exception {
        String fileName = "soundActionWithRepeatFlagTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        InputStream is = FileUtil.getInputStreamForFile(sourceFolder + "sample.aif");
        PdfStream sound1 = new PdfStream(document, is);
        sound1.put(PdfName.R, new PdfNumber(32117));
        sound1.put(PdfName.E, PdfName.Signed);
        sound1.put(PdfName.B, new PdfNumber(16));
        sound1.put(PdfName.C, new PdfNumber(1));

        document.getPage(2)
                .setAdditionalAction(PdfName.O, PdfAction.createSound(sound1,1f, false, true, false));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void soundActionWithToBigVolumeTest() throws Exception {
        PdfDocument document = createDocument(new PdfWriter(new ByteArrayOutputStream()), false);

        InputStream is = FileUtil.getInputStreamForFile(sourceFolder + "sample.aif");
        PdfStream sound1 = new PdfStream(document, is);
        sound1.put(PdfName.R, new PdfNumber(32117));
        sound1.put(PdfName.E, PdfName.Signed);
        sound1.put(PdfName.B, new PdfNumber(16));
        sound1.put(PdfName.C, new PdfNumber(1));

        try {
            document.getPage(2)
                    .setAdditionalAction(PdfName.O, PdfAction.createSound(sound1, 1.1f, false, false, false));
            Assertions.fail("Exception not thrown");
        } catch (Exception e) {
            Assertions.assertEquals("volume", e.getMessage());
        }
        document.close();
    }

    @Test
    public void soundActionWithToLowVolumeTest() throws Exception {
        PdfDocument document = createDocument(new PdfWriter(new ByteArrayOutputStream()), false);

        InputStream is = FileUtil.getInputStreamForFile(sourceFolder + "sample.aif");
        PdfStream sound1 = new PdfStream(document, is);
        sound1.put(PdfName.R, new PdfNumber(32117));
        sound1.put(PdfName.E, PdfName.Signed);
        sound1.put(PdfName.B, new PdfNumber(16));
        sound1.put(PdfName.C, new PdfNumber(1));

        try {
            document.getPage(2)
                    .setAdditionalAction(PdfName.O, PdfAction.createSound(sound1, -1.1f, false, false, false));
            Assertions.fail("Exception not thrown");
        } catch (Exception e) {
            Assertions.assertEquals("volume", e.getMessage());
        }
        document.close();
    }

    @Test
    public void ocgStateTest() throws Exception {
        PdfName stateName = PdfName.ON;

        PdfDictionary ocgDict1 = new PdfDictionary();
        ocgDict1.put(PdfName.Type, PdfName.OCG);
        ocgDict1.put(PdfName.Name, new PdfName("ocg1"));

        PdfDictionary ocgDict2 = new PdfDictionary();
        ocgDict2.put(PdfName.Type, PdfName.OCG);
        ocgDict2.put(PdfName.Name, new PdfName("ocg2"));

        List<PdfDictionary> dicts = new ArrayList<>();
        dicts.add(ocgDict1);
        dicts.add(ocgDict2);

        List<PdfActionOcgState> ocgStates = new ArrayList<>();
        ocgStates.add(new PdfActionOcgState(stateName, dicts));

        String fileName = "ocgStateTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);
        document.getPage(1).setAdditionalAction(PdfName.O, PdfAction.createSetOcgState(ocgStates));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void launchActionTest() throws Exception {
        String fileName = "launchActionTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        document.getPage(1).setAdditionalAction(PdfName.O, PdfAction.createLaunch(new PdfStringFS("launch.sh")));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void launchActionOnNewWindowTest() throws Exception {
        String fileName = "launchActionOnNewWindowTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        document.getPage(1).setAdditionalAction(PdfName.O,
                PdfAction.createLaunch(new PdfStringFS("launch.sh"), true));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void createHiddenAnnotationTest() throws Exception {
        String fileName = "createHiddenAnnotationTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        PdfAnnotation annotation = new PdfLineAnnotation(new Rectangle(10, 10, 200, 200),
                new float[] {50, 750, 50, 750});
        document.getPage(1).setAdditionalAction(PdfName.O, PdfAction.createHide(annotation, true));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void createHiddenAnnotationsTest() throws Exception {
        String fileName = "createHiddenAnnotationsTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        PdfAnnotation[] annotations = new PdfAnnotation[] {
                new PdfLineAnnotation(new Rectangle(10, 10, 200, 200), new float[] {50, 750, 50, 750}),
                new PdfLineAnnotation(new Rectangle(200, 200, 200, 200), new float[] {50, 750, 50, 750})
        };
        document.getPage(1).setAdditionalAction(PdfName.O, PdfAction.createHide(annotations, true));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void createHiddenByFieldNameTest() throws Exception {
        String fileName = "createHiddenByFieldNameTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        document.getPage(1).setAdditionalAction(PdfName.O, PdfAction.createHide("name", true));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void createHiddenByFieldNamesTest() throws Exception {
        String fileName = "createHiddenByFieldNamesTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        document.getPage(1).setAdditionalAction(PdfName.O, PdfAction.createHide(new String[] {"name1", "name2"}, true));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void createNamedTest() throws Exception {
        String fileName = "createNamedTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        document.getPage(1).setAdditionalAction(PdfName.O, PdfAction.createNamed(PdfName.LastPage));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void createJavaScriptTest() throws Exception {
        String fileName = "createJavaScriptTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        String javaScriptRotatePages = "this.setPageRotations(0,2,90)";
        document.getPage(1).setAdditionalAction(PdfName.O, PdfAction.createJavaScript(javaScriptRotatePages));
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void soundAndNextJavaScriptActionTest() throws Exception {
        String fileName = "soundAndNextJavaScriptActionTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        InputStream is = FileUtil.getInputStreamForFile(sourceFolder + "sample.aif");
        PdfStream sound1 = new PdfStream(document, is);
        sound1.put(PdfName.R, new PdfNumber(32117));
        sound1.put(PdfName.E, PdfName.Signed);
        sound1.put(PdfName.B, new PdfNumber(16));
        sound1.put(PdfName.C, new PdfNumber(1));

        PdfAction action = PdfAction.createSound(sound1);
        action.next(PdfAction.createJavaScript("this.setPageRotations(0,2,90)"));
        document.getPage(2).setAdditionalAction(PdfName.O, action);
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    @Test
    public void soundAndTwoNextJavaScriptActionTest() throws Exception {
        String fileName = "soundAndTwoNextJavaScriptActionTest.pdf";
        PdfDocument document = createDocument(CompareTool.createTestPdfWriter(destinationFolder + fileName), false);

        InputStream is = FileUtil.getInputStreamForFile(sourceFolder + "sample.aif");
        PdfStream sound1 = new PdfStream(document, is);
        sound1.put(PdfName.R, new PdfNumber(32117));
        sound1.put(PdfName.E, PdfName.Signed);
        sound1.put(PdfName.B, new PdfNumber(16));
        sound1.put(PdfName.C, new PdfNumber(1));

        PdfAction action = PdfAction.createSound(sound1);
        action.next(PdfAction.createJavaScript("this.setPageRotations(0,2,90)"));
        action.next(PdfAction.createJavaScript("this.setPageRotations(0,2,180)"));
        document.getPage(2).setAdditionalAction(PdfName.O, action);
        document.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + fileName,
                sourceFolder + "cmp_" + fileName, destinationFolder, "diff_"));
    }

    private PdfDocument createDocument(PdfWriter writer, boolean flushPages) {
        PdfDocument document = new PdfDocument(writer);
        PdfPage p1 = document.addNewPage();
        PdfStream str1 = p1.getFirstContentStream();
        str1.getOutputStream().writeString("1 0 0 rg 100 600 100 100 re f\n");
        if (flushPages)
            p1.flush();
        PdfPage p2 = document.addNewPage();
        PdfStream str2 = p2.getFirstContentStream();
        str2.getOutputStream().writeString("0 1 0 rg 100 600 100 100 re f\n");
        if (flushPages)
            p2.flush();
        PdfPage p3 = document.addNewPage();
        PdfStream str3 = p3.getFirstContentStream();
        str3.getOutputStream().writeString("0 0 1 rg 100 600 100 100 re f\n");
        if (flushPages)
            p3.flush();
        return document;
    }

}