PdfNameTreeTest.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.font.constants.StandardFonts;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.annot.PdfAnnotationAppearance;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.filespec.PdfFileSpec;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.test.ExtendedITextTest;
import com.itextpdf.test.TestUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
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 PdfNameTreeTest extends ExtendedITextTest {
public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/pdf/PdfNameTreeTest/";
public static final String destinationFolder = TestUtil.getOutputPath() + "/kernel/pdf/PdfNameTreeTest/";
@BeforeAll
public static void beforeClass() {
createOrClearDestinationFolder(destinationFolder);
}
@AfterAll
public static void afterClass() {
CompareTool.cleanup(destinationFolder);
}
@Test
public void embeddedFileAndJavascriptTest() throws IOException {
PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + "FileWithSingleAttachment.pdf"));
PdfNameTree embeddedFilesNameTree = pdfDocument.getCatalog().getNameTree(PdfName.EmbeddedFiles);
Map<PdfString, PdfObject> objs = embeddedFilesNameTree.getNames();
PdfNameTree javascript = pdfDocument.getCatalog().getNameTree(PdfName.JavaScript);
Map<PdfString, PdfObject> objs2 = javascript.getNames();
pdfDocument.close();
Assertions.assertEquals(1, objs.size());
Assertions.assertEquals(1, objs2.size());
}
@Test
public void embeddedFileAddedInAppendModeTest() throws IOException{
//Create input document
ByteArrayOutputStream boasEmpty = new ByteArrayOutputStream();
PdfWriter emptyDocWriter = new PdfWriter(boasEmpty);
PdfDocument emptyDoc = new PdfDocument(emptyDocWriter);
emptyDoc.addNewPage();
PdfDictionary emptyNamesDic = new PdfDictionary();
emptyNamesDic.makeIndirect(emptyDoc);
emptyDoc.getCatalog().getPdfObject().put(PdfName.Names,emptyNamesDic);
emptyDoc.close();
//Create input document
ByteArrayOutputStream boasAttached = new ByteArrayOutputStream();
PdfWriter attachDocWriter = new PdfWriter(boasAttached);
PdfDocument attachDoc = new PdfDocument(attachDocWriter);
attachDoc.addNewPage();
attachDoc.close();
//Attach file in append mode
PdfReader appendReader = new PdfReader(new ByteArrayInputStream(boasEmpty.toByteArray()));
ByteArrayOutputStream boasAppend = new ByteArrayOutputStream();
PdfWriter appendWriter = new PdfWriter(boasAppend);
PdfDocument appendDoc = new PdfDocument(appendReader,appendWriter,new StampingProperties().useAppendMode());
appendDoc.addFileAttachment("Test File", PdfFileSpec.createEmbeddedFileSpec(appendDoc,boasAttached.toByteArray(),"Append Embedded File test","Test file",null));
appendDoc.close();
//Check final result
PdfReader finalReader = new PdfReader(new ByteArrayInputStream(boasAppend.toByteArray()));
PdfDocument finalDoc = new PdfDocument(finalReader);
PdfNameTree embeddedFilesNameTree = finalDoc.getCatalog().getNameTree(PdfName.EmbeddedFiles);
Map<PdfString, PdfObject> embeddedFilesMap = embeddedFilesNameTree.getNames();
Assertions.assertTrue(embeddedFilesMap.size()>0);
Assertions.assertTrue(embeddedFilesMap.containsKey(new PdfString("Test File")));
}
@Test
public void annotationAppearanceTest() throws IOException {
PdfDocument pdfDocument = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "AnnotationAppearanceTest.pdf"));
PdfPage page = pdfDocument.addNewPage();
PdfCanvas canvas = new PdfCanvas(page);
canvas.setFillColor(ColorConstants.MAGENTA).beginText().setFontAndSize(PdfFontFactory.createFont(StandardFonts.TIMES_ROMAN), 30)
.setTextMatrix(25, 500).showText("This file has AP key in Names dictionary").endText();
PdfArray array = new PdfArray();
array.add(new PdfString("normalAppearance"));
array.add(new PdfAnnotationAppearance().setState(PdfName.N, new PdfFormXObject(new Rectangle(50, 50 , 50, 50))).getPdfObject());
PdfDictionary dict = new PdfDictionary();
dict.put(PdfName.Names, array);
PdfDictionary dictionary = new PdfDictionary();
dictionary.put(PdfName.AP, dict);
pdfDocument.getCatalog().getPdfObject().put(PdfName.Names, dictionary);
PdfNameTree appearance = pdfDocument.getCatalog().getNameTree(PdfName.AP);
Map<PdfString, PdfObject> objs = appearance.getNames();
pdfDocument.close();
Assertions.assertEquals(1, objs.size());
}
@Test
public void setModifiedFlagTest() throws IOException {
testSetModified(false);
}
@Test
public void setModifiedFlagAppendModeTest() throws IOException {
testSetModified(true);
}
@Test
public void checkNamesOrder() throws IOException {
PdfDocument doc = new PdfDocument(new PdfReader(sourceFolder + "namedDestinations.pdf"));
final List<String> expectedNames = new ArrayList<>();
expectedNames.add("Destination_1");
expectedNames.add("Destination_2");
expectedNames.add("Destination_3");
expectedNames.add("Destination_4");
expectedNames.add("Destination_5");
System.out.println("Expected names: " + expectedNames);
for (int i = 0; i < 10; i++) {
IPdfNameTreeAccess names = doc.getCatalog().getNameTree(PdfName.Dests);
List<String> actualNames = new ArrayList<>();
for (PdfString name : names.getKeys()) {
actualNames.add(name.toUnicodeString());
}
System.out.println("Actual names: " + actualNames);
Assertions.assertEquals(expectedNames, actualNames);
}
doc.close();
}
private static void testSetModified(boolean isAppendMode) throws IOException {
PdfString[] expectedKeys = {
new PdfString("new_key1"),
new PdfString("new_key2"),
new PdfString("new_key3"),
};
ByteArrayOutputStream sourceFile = createDocumentInMemory();
ByteArrayOutputStream modifiedFile = new ByteArrayOutputStream();
PdfReader reader = new PdfReader(new ByteArrayInputStream(sourceFile.toByteArray()));
PdfDocument pdfDoc = isAppendMode
? new PdfDocument(reader, new PdfWriter(modifiedFile), new StampingProperties().useAppendMode())
: new PdfDocument(reader, new PdfWriter(modifiedFile));
PdfNameTree nameTree = pdfDoc.getCatalog().getNameTree(PdfName.Dests);
Map<PdfString, PdfObject> names = nameTree.getNames();
List<PdfString> keys = new ArrayList<>(names.keySet());
for (int i = 0; i < keys.size(); i++) {
names.put(expectedKeys[i], names.get(keys.get(i)));
names.remove(keys.get(i));
}
nameTree.setModified();
pdfDoc.close();
reader = new PdfReader(new ByteArrayInputStream(modifiedFile.toByteArray()));
pdfDoc = new PdfDocument(reader);
nameTree = pdfDoc.getCatalog().getNameTree(PdfName.Dests);
Set<PdfString> actualKeys = nameTree.getNames().keySet();
Assertions.assertArrayEquals(expectedKeys, actualKeys.toArray());
}
private static ByteArrayOutputStream createDocumentInMemory() {
ByteArrayOutputStream boas = new ByteArrayOutputStream();
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(boas));
pdfDoc.addNewPage();
pdfDoc.getCatalog().getNameTree(PdfName.Dests).addEntry("key1",
new PdfArray(new float[] {0, 0, 0, 0}));
pdfDoc.getCatalog().getNameTree(PdfName.Dests).addEntry("key2",
new PdfArray(new float[] {1, 1, 1, 1}));
pdfDoc.getCatalog().getNameTree(PdfName.Dests).addEntry("key3",
new PdfArray(new float[] {2, 2, 2, 2}));
pdfDoc.close();
return boas;
}
}