PdfPagesTest.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.MessageFormatUtil;
import com.itextpdf.io.image.ImageDataFactory;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.io.source.RandomAccessSourceFactory;
import com.itextpdf.kernel.colors.ColorConstants;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.extgstate.PdfExtGState;
import com.itextpdf.kernel.pdf.xobject.PdfFormXObject;
import com.itextpdf.kernel.pdf.xobject.PdfImageXObject;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.test.AssertUtil;
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.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
@Tag("IntegrationTest")
public class PdfPagesTest extends ExtendedITextTest {
public static final String DESTINATION_FOLDER = TestUtil.getOutputPath() + "/kernel/pdf/PdfPagesTest/";
public static final String SOURCE_FOLDER = "./src/test/resources/com/itextpdf/kernel/pdf/PdfPagesTest/";
private static final PdfName PageNum = new PdfName("PageNum");
@BeforeAll
public static void setup() {
createDestinationFolder(DESTINATION_FOLDER);
}
@AfterAll
public static void afterClass() {
CompareTool.cleanup(DESTINATION_FOLDER);
}
@Test
public void hugeNumberOfPagesWithOnePageTest() throws IOException {
PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "hugeNumberOfPagesWithOnePage.pdf"),
new PdfWriter(new ByteArrayOutputStream()));
PdfPage page = new PdfPage(pdfDoc, pdfDoc.getDefaultPageSize());
AssertUtil.doesNotThrow(() -> pdfDoc.addPage(1, page));
}
@Test
public void countDontCorrespondToRealTest() throws IOException {
PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "countDontCorrespondToReal.pdf"),
new PdfWriter(new ByteArrayOutputStream()));
PdfPage page = new PdfPage(pdfDoc, pdfDoc.getDefaultPageSize());
AssertUtil.doesNotThrow(() -> pdfDoc.addPage(1, page));
// we don't expect that Count will be different from real number of pages
Assertions.assertThrows(NullPointerException.class, () -> pdfDoc.close());
}
@Test
public void simplePagesTest() throws IOException {
String filename = "simplePagesTest.pdf";
int pageCount = 111;
PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename));
for (int i = 0; i < pageCount; i++) {
PdfPage page = pdfDoc.addNewPage();
page.getPdfObject().put(PageNum, new PdfNumber(i + 1));
page.flush();
}
pdfDoc.close();
verifyPagesOrder(DESTINATION_FOLDER + filename, pageCount);
}
@Test
public void reversePagesTest() throws IOException {
String filename = "reversePagesTest.pdf";
int pageCount = 111;
PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename));
for (int i = pageCount; i > 0; i--) {
PdfPage page = new PdfPage(pdfDoc, pdfDoc.getDefaultPageSize());
pdfDoc.addPage(1, page);
page.getPdfObject().put(PageNum, new PdfNumber(i));
page.flush();
}
pdfDoc.close();
verifyPagesOrder(DESTINATION_FOLDER + filename, pageCount);
}
@Test
public void reversePagesTest2() throws Exception {
String filename = "1000PagesDocument_reversed.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + "1000PagesDocument.pdf"),
CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename));
int n = pdfDoc.getNumberOfPages();
for (int i = n - 1; i > 0; --i) {
pdfDoc.movePage(i, n + 1);
}
pdfDoc.close();
Assertions.assertNull(new CompareTool()
.compareByContent(DESTINATION_FOLDER + filename, SOURCE_FOLDER + "cmp_" + filename, DESTINATION_FOLDER,
"diff"));
}
@Test
public void randomObjectPagesTest() throws IOException {
String filename = "randomObjectPagesTest.pdf";
int pageCount = 10000;
int[] indexes = new int[pageCount];
for (int i = 0; i < indexes.length; i++) {
indexes[i] = i + 1;
}
Random rnd = new Random();
for (int i = indexes.length - 1; i > 0; i--) {
int index = rnd.nextInt(i + 1);
int a = indexes[index];
indexes[index] = indexes[i];
indexes[i] = a;
}
PdfDocument document = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename));
PdfPage[] pages = new PdfPage[pageCount];
for (int i = 0; i < indexes.length; i++) {
PdfPage page = document.addNewPage();
page.getPdfObject().put(PageNum, new PdfNumber(indexes[i]));
//page.flush();
pages[indexes[i] - 1] = page;
}
int testPageXref = document.getPage(1000).getPdfObject().getIndirectReference().getObjNumber();
document.movePage(1000, 1000);
Assertions.assertEquals(testPageXref, document.getPage(1000).getPdfObject().getIndirectReference().getObjNumber());
for (int i = 0; i < pages.length; i++) {
Assertions.assertTrue(document.movePage(pages[i], i + 1), "Move page");
}
document.close();
verifyPagesOrder(DESTINATION_FOLDER + filename, pageCount);
}
@Test
// Android-Conversion-Ignore-Test (TODO DEVSIX-8114 Fix randomNumberPagesTest test)
public void randomNumberPagesTest() throws IOException {
String filename = "randomNumberPagesTest.pdf";
int pageCount = 1000;
int[] indexes = new int[pageCount];
for (int i = 0; i < indexes.length; i++) {
indexes[i] = i + 1;
}
Random rnd = new Random();
for (int i = indexes.length - 1; i > 0; i--) {
int index = rnd.nextInt(i + 1);
int a = indexes[index];
indexes[index] = indexes[i];
indexes[i] = a;
}
PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename));
for (int i = 0; i < indexes.length; i++) {
PdfPage page = pdfDoc.addNewPage();
page.getPdfObject().put(PageNum, new PdfNumber(indexes[i]));
}
for (int i = 1; i < pageCount; i++) {
for (int j = i + 1; j <= pageCount; j++) {
int j_page = pdfDoc.getPage(j).getPdfObject().getAsNumber(PageNum).intValue();
int i_page = pdfDoc.getPage(i).getPdfObject().getAsNumber(PageNum).intValue();
if (j_page < i_page) {
pdfDoc.movePage(i, j);
pdfDoc.movePage(j, i);
}
}
Assertions.assertTrue(verifyIntegrity(pdfDoc.getCatalog().getPageTree()) == -1);
}
pdfDoc.close();
verifyPagesOrder(DESTINATION_FOLDER + filename, pageCount);
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.REMOVING_PAGE_HAS_ALREADY_BEEN_FLUSHED)
})
public void insertFlushedPageTest() {
PdfWriter writer = new PdfWriter(new ByteArrayOutputStream());
PdfDocument pdfDoc = new PdfDocument(writer);
PdfPage page = pdfDoc.addNewPage();
page.flush();
pdfDoc.removePage(page);
Exception e = Assertions.assertThrows(PdfException.class,
() -> pdfDoc.addPage(1, page)
);
Assertions.assertEquals(KernelExceptionMessageConstant.FLUSHED_PAGE_CANNOT_BE_ADDED_OR_INSERTED, e.getMessage());
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.REMOVING_PAGE_HAS_ALREADY_BEEN_FLUSHED)
})
public void addFlushedPageTest() {
PdfWriter writer = new PdfWriter(new ByteArrayOutputStream());
PdfDocument pdfDoc = new PdfDocument(writer);
PdfPage page = pdfDoc.addNewPage();
page.flush();
pdfDoc.removePage(page);
Exception e = Assertions.assertThrows(PdfException.class,
() -> pdfDoc.addPage(page)
);
Assertions.assertEquals(KernelExceptionMessageConstant.FLUSHED_PAGE_CANNOT_BE_ADDED_OR_INSERTED, e.getMessage());
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.REMOVING_PAGE_HAS_ALREADY_BEEN_FLUSHED, count = 2)
})
public void removeFlushedPage() throws IOException {
String filename = "removeFlushedPage.pdf";
int pageCount = 10;
PdfDocument pdfDoc = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename));
PdfPage removedPage = pdfDoc.addNewPage();
int removedPageObjectNumber = removedPage.getPdfObject().getIndirectReference().getObjNumber();
removedPage.flush();
pdfDoc.removePage(removedPage);
for (int i = 0; i < pageCount; i++) {
PdfPage page = pdfDoc.addNewPage();
page.getPdfObject().put(PageNum, new PdfNumber(i + 1));
page.flush();
}
Assertions.assertTrue(pdfDoc.removePage(pdfDoc.getPage(pageCount)), "Remove last page");
Assertions.assertFalse(pdfDoc.getXref().get(removedPageObjectNumber).checkState(PdfObject.FREE), "Free reference");
pdfDoc.close();
verifyPagesOrder(DESTINATION_FOLDER + filename, pageCount - 1);
}
@Test
public void removeFlushedPageFromTaggedDocument() {
try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) {
pdfDocument.setTagged();
pdfDocument.addNewPage();
pdfDocument.getPage(1).flush();
Exception e = Assertions.assertThrows(PdfException.class,
() -> pdfDocument.removePage(1)
);
Assertions.assertEquals(KernelExceptionMessageConstant.FLUSHED_PAGE_CANNOT_BE_REMOVED, e.getMessage());
}
}
@Test
public void removeFlushedPageFromDocumentWithAcroForm() {
try (PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()))) {
pdfDocument.getCatalog().put(PdfName.AcroForm, new PdfDictionary());
pdfDocument.addNewPage();
pdfDocument.getPage(1).flush();
Exception e = Assertions.assertThrows(PdfException.class,
() -> pdfDocument.removePage(1)
);
Assertions.assertEquals(KernelExceptionMessageConstant.FLUSHED_PAGE_CANNOT_BE_REMOVED, e.getMessage());
}
}
@Test
public void testInheritedResources() throws IOException {
PdfDocument pdfDocument = new PdfDocument(new PdfReader(SOURCE_FOLDER + "simpleInheritedResources.pdf"));
PdfPage page = pdfDocument.getPage(1);
PdfDictionary dict = page.getResources().getResource(PdfName.ExtGState);
Assertions.assertEquals(2, dict.size());
PdfExtGState gState = new PdfExtGState((PdfDictionary) dict.get(new PdfName("Gs1")));
Assertions.assertEquals(10, gState.getLineWidth().intValue());
}
@Test
public void readFormXObjectsWithCircularReferencesInResources() throws IOException {
// given input file contains circular reference in resources of form xobjects
// (form xobjects are nested inside each other)
String input = SOURCE_FOLDER + "circularReferencesInResources.pdf";
PdfReader reader1 = new PdfReader(input);
PdfDocument inputPdfDoc1 = new PdfDocument(reader1);
PdfPage page = inputPdfDoc1.getPage(1);
PdfResources resources = page.getResources();
List<PdfFormXObject> formXObjects = new ArrayList<>();
// We just try to work with resources in arbitrary way and make sure that circular reference
// doesn't block it. However it is expected that PdfResources doesn't try to "look in deep"
// and recursively resolves resources, so this test should never meet any issues.
for (PdfName xObjName : resources.getResourceNames(PdfName.XObject)) {
PdfFormXObject form = resources.getForm(xObjName);
if (form != null) {
formXObjects.add(form);
}
}
// ensure resources XObject entry is read correctly
Assertions.assertEquals(2, formXObjects.size());
}
@Test
public void testInheritedResourcesUpdate() throws IOException, InterruptedException {
PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "simpleInheritedResources.pdf"),
CompareTool.createTestPdfWriter(DESTINATION_FOLDER + "updateInheritedResources.pdf")
.setCompressionLevel(CompressionConstants.NO_COMPRESSION));
PdfName newGsName = pdfDoc.getPage(1).getResources().addExtGState(new PdfExtGState().setLineWidth(30));
int gsCount = pdfDoc.getPage(1).getResources().getResource(PdfName.ExtGState).size();
pdfDoc.close();
String compareResult = new CompareTool().compareByContent(
DESTINATION_FOLDER + "updateInheritedResources.pdf",
SOURCE_FOLDER + "cmp_" + "updateInheritedResources.pdf",
DESTINATION_FOLDER, "diff");
Assertions.assertEquals(3, gsCount);
Assertions.assertEquals("Gs3", newGsName.getValue());
Assertions.assertNull(compareResult);
}
@Test
//TODO: DEVSIX-1643 Inherited resources aren't copied on page reordering
public void reorderInheritedResourcesTest() throws IOException, InterruptedException {
PdfDocument pdfDoc = new PdfDocument(
new PdfReader(SOURCE_FOLDER + "inheritedFontResources.pdf"),
CompareTool.createTestPdfWriter(DESTINATION_FOLDER + "reorderInheritedFontResources.pdf")
);
pdfDoc.movePage(1, pdfDoc.getNumberOfPages() + 1);
pdfDoc.removePage(1);
pdfDoc.close();
String compareResult = new CompareTool().compareByContent(
DESTINATION_FOLDER + "reorderInheritedFontResources.pdf",
SOURCE_FOLDER + "cmp_reorderInheritedFontResources.pdf",
DESTINATION_FOLDER, "diff_reorderInheritedFontResources_");
Assertions.assertNull(compareResult);
}
@Test
public void getPageByDictionary() throws IOException {
String filename = SOURCE_FOLDER + "1000PagesDocument.pdf";
PdfReader reader = new PdfReader(filename);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfObject[] pageDictionaries = new PdfObject[] {
pdfDoc.getPdfObject(4),
pdfDoc.getPdfObject(255),
pdfDoc.getPdfObject(512),
pdfDoc.getPdfObject(1023),
pdfDoc.getPdfObject(2049),
pdfDoc.getPdfObject(3100)
};
for (PdfObject pageObject : pageDictionaries) {
PdfDictionary pageDictionary = (PdfDictionary) pageObject;
Assertions.assertEquals(PdfName.Page, pageDictionary.get(PdfName.Type));
PdfPage page = pdfDoc.getPage(pageDictionary);
Assertions.assertEquals(pageDictionary, page.getPdfObject());
}
pdfDoc.close();
}
@Test
public void removePageWithFormFieldsTest() throws IOException, InterruptedException {
String testName = "docWithFieldsRemovePage.pdf";
String outPdf = DESTINATION_FOLDER + testName;
String sourceFile = SOURCE_FOLDER + "docWithFields.pdf";
try (PdfDocument pdfDoc = new PdfDocument(new PdfReader(sourceFile), CompareTool.createTestPdfWriter(outPdf))) {
pdfDoc.removePage(1);
}
Assertions.assertNull(
new CompareTool().compareByContent(outPdf, SOURCE_FOLDER + "cmp_" + testName, DESTINATION_FOLDER));
}
@Test
public void getPageSizeWithInheritedMediaBox() throws IOException {
double eps = 0.0000001;
String filename = SOURCE_FOLDER + "inheritedMediaBox.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfReader(filename));
Assertions.assertEquals(0, pdfDoc.getPage(1).getPageSize().getLeft(), eps);
Assertions.assertEquals(0, pdfDoc.getPage(1).getPageSize().getBottom(), eps);
Assertions.assertEquals(595, pdfDoc.getPage(1).getPageSize().getRight(), eps);
Assertions.assertEquals(842, pdfDoc.getPage(1).getPageSize().getTop(), eps);
pdfDoc.close();
}
@Test
public void pageThumbnailTest() throws Exception {
String filename = "pageThumbnail.pdf";
String imageSrc = "icon.jpg";
PdfDocument pdfDoc = new PdfDocument(
CompareTool.createTestPdfWriter(DESTINATION_FOLDER + filename).setCompressionLevel(CompressionConstants.NO_COMPRESSION));
PdfPage page = pdfDoc.addNewPage()
.setThumbnailImage(new PdfImageXObject(ImageDataFactory.create(SOURCE_FOLDER + imageSrc)));
new PdfCanvas(page).setFillColor(ColorConstants.RED).rectangle(100, 100, 400, 400).fill();
pdfDoc.close();
Assertions.assertNull(new CompareTool()
.compareByContent(DESTINATION_FOLDER + filename, SOURCE_FOLDER + "cmp_" + filename, DESTINATION_FOLDER,
"diff"));
}
@Test
public void rotationPagesRotationTest() throws IOException {
String filename = "singlePageDocumentWithRotation.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfReader(SOURCE_FOLDER + filename));
PdfPage page = pdfDoc.getPage(1);
Assertions.assertEquals(90, page.getRotation(), "Inherited value is invalid");
}
@Test
public void pageTreeCleanupParentRefTest() throws IOException {
String src = SOURCE_FOLDER + "CatalogWithPageAndPagesEntries.pdf";
String dest = DESTINATION_FOLDER + "CatalogWithPageAndPagesEntries_opened.pdf";
PdfReader reader = new PdfReader(src);
PdfWriter writer = CompareTool.createTestPdfWriter(dest);
PdfDocument pdfDoc = new PdfDocument(reader, writer);
pdfDoc.close();
Assertions.assertTrue(testPageTreeParentsValid(src) && testPageTreeParentsValid(dest));
}
@Test
public void pdfNumberInPageContentArrayTest() throws IOException {
String src = SOURCE_FOLDER + "pdfNumberInPageContentArray.pdf";
String dest = DESTINATION_FOLDER + "pdfNumberInPageContentArray_saved.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfReader(src), CompareTool.createTestPdfWriter(dest));
pdfDoc.close();
// test is mainly to ensure document is successfully opened-and-closed without exceptions
pdfDoc = new PdfDocument(CompareTool.createOutputReader(dest));
PdfObject pageDictWithInvalidContents = pdfDoc.getPdfObject(10);
PdfArray invalidContentsArray = ((PdfDictionary) pageDictWithInvalidContents).getAsArray(PdfName.Contents);
Assertions.assertEquals(5, invalidContentsArray.size());
Assertions.assertFalse(invalidContentsArray.get(0).isStream());
Assertions.assertFalse(invalidContentsArray.get(1).isStream());
Assertions.assertFalse(invalidContentsArray.get(2).isStream());
Assertions.assertFalse(invalidContentsArray.get(3).isStream());
Assertions.assertTrue(invalidContentsArray.get(4).isStream());
}
private boolean testPageTreeParentsValid(String src) throws com.itextpdf.io.exceptions.IOException, java.io.IOException {
boolean valid = true;
PdfReader reader = CompareTool.createOutputReader(src);
PdfDocument pdfDocument = new PdfDocument(reader);
PdfDictionary page_root = pdfDocument.getCatalog().getPdfObject().getAsDictionary(PdfName.Pages);
for (int x = 1; x < pdfDocument.getNumberOfPdfObjects(); x++) {
PdfObject obj = pdfDocument.getPdfObject(x);
if (obj != null && obj.isDictionary() && ((PdfDictionary) obj).getAsName(PdfName.Type) != null
&& ((PdfDictionary) obj).getAsName(PdfName.Type).equals(PdfName.Pages)) {
if (obj != page_root) {
PdfDictionary parent = ((PdfDictionary) obj).getAsDictionary(PdfName.Parent);
if (parent == null) {
System.out.println(obj);
valid = false;
}
}
}
}
return valid;
}
@Test
public void testExcessiveXrefEntriesForCopyXObject() throws IOException {
PdfDocument inputPdf = new PdfDocument(new PdfReader(SOURCE_FOLDER + "input500.pdf"));
PdfDocument outputPdf = new PdfDocument(CompareTool.createTestPdfWriter(DESTINATION_FOLDER + "output500.pdf"));
float scaleX = 595f / 612f;
float scaleY = 842f / 792f;
for (int i = 1; i <= inputPdf.getNumberOfPages(); ++i) {
PdfPage sourcePage = inputPdf.getPage(i);
PdfFormXObject pageCopy = sourcePage.copyAsFormXObject(outputPdf);
PdfPage page = outputPdf.addNewPage(PageSize.A4);
PdfCanvas outputCanvas = new PdfCanvas(page);
outputCanvas.addXObjectWithTransformationMatrix(pageCopy, scaleX, 0, 0, scaleY, 0, 0);
page.flush();
}
outputPdf.close();
inputPdf.close();
Assertions.assertNotNull(outputPdf.getXref());
Assertions.assertEquals(500, outputPdf.getXref().size() - inputPdf.getXref().size());
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.WRONG_MEDIABOX_SIZE_TOO_MANY_ARGUMENTS, count = 1),
})
public void pageGetMediaBoxTooManyArgumentsTest() throws IOException {
PdfReader reader = new PdfReader(SOURCE_FOLDER + "helloWorldMediaboxTooManyArguments.pdf");
Rectangle expected = new Rectangle(0, 0, 375, 300);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfPage pageOne = pdfDoc.getPage(1);
Rectangle actual = pageOne.getPageSize();
Assertions.assertTrue(expected.equalsWithEpsilon(actual));
}
@Test
public void closeDocumentWithRecursivePagesNodeReferencesThrowsExTest() throws IOException {
try (PdfReader reader = new PdfReader(SOURCE_FOLDER + "recursivePagesNodeReference.pdf");
PdfWriter writer = new PdfWriter(new ByteArrayOutputStream());
) {
PdfDocument pdfDocument = new PdfDocument(reader, writer);
Exception e = Assertions.assertThrows(PdfException.class, () -> pdfDocument.close());
Assertions.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.INVALID_PAGE_STRUCTURE, 2), e.getMessage());
}
}
@Test
public void getPageWithRecursivePagesNodeReferenceInAppendModeThrowExTest() throws IOException {
try (PdfReader reader = new PdfReader(SOURCE_FOLDER + "recursivePagesNodeReference.pdf");
PdfWriter writer = new PdfWriter(new ByteArrayOutputStream());
PdfDocument pdfDocument = new PdfDocument(reader, writer, new StampingProperties().useAppendMode());
) {
Assertions.assertEquals(2, pdfDocument.getNumberOfPages());
Assertions.assertNotNull(pdfDocument.getPage(1));
Exception e = Assertions.assertThrows(PdfException.class, () -> pdfDocument.getPage(2));
Assertions.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.INVALID_PAGE_STRUCTURE, 2), e.getMessage());
}
}
@Test
public void closeDocumentWithRecursivePagesNodeInAppendModeDoesNotThrowsTest() throws IOException {
try (PdfReader reader = new PdfReader(SOURCE_FOLDER + "recursivePagesNodeReference.pdf");
PdfWriter writer = new PdfWriter(new ByteArrayOutputStream());
PdfDocument pdfDocument = new PdfDocument(reader, writer, new StampingProperties().useAppendMode());
) {
AssertUtil.doesNotThrow(() -> pdfDocument.close());
}
}
@Test
public void pageGetMediaBoxNotEnoughArgumentsTest() throws IOException {
PdfReader reader = new PdfReader(SOURCE_FOLDER + "helloWorldMediaboxNotEnoughArguments.pdf");
PdfDocument pdfDoc = new PdfDocument(reader);
PdfPage pageOne = pdfDoc.getPage(1);
Exception e = Assertions.assertThrows(PdfException.class, () -> pageOne.getPageSize());
Assertions.assertEquals(MessageFormatUtil.format(KernelExceptionMessageConstant.WRONG_MEDIA_BOX_SIZE_TOO_FEW_ARGUMENTS, 3), e.getMessage());
}
@Test
public void insertIntermediateParentTest() throws IOException {
String filename = "insertIntermediateParentTest.pdf";
PdfReader reader = new PdfReader(SOURCE_FOLDER + filename);
PdfWriter writer = new PdfWriter(new ByteArrayOutputStream());
PdfDocument pdfDoc = new PdfDocument(reader, writer, new StampingProperties().useAppendMode());
PdfPage page = pdfDoc.getFirstPage();
PdfPages pdfPages = new PdfPages(page.parentPages.getFrom(), pdfDoc, page.parentPages);
page.parentPages.getKids().set(0, pdfPages.getPdfObject());
page.parentPages.decrementCount();
pdfPages.addPage(page.getPdfObject());
pdfDoc.close();
Assertions.assertTrue(page.getPdfObject().isModified());
}
@Test
public void verifyPagesAreNotReadOnOpenTest() throws IOException {
String srcFile = SOURCE_FOLDER + "taggedOnePage.pdf";
CustomPdfReader reader = new CustomPdfReader(srcFile);
PdfDocument document = new PdfDocument(reader);
document.close();
Assertions.assertFalse(reader.pagesAreRead);
}
@Test
public void copyAnnotationWithoutSubtypeTest() throws IOException {
try (
ByteArrayOutputStream baos = createSourceDocumentWithEmptyAnnotation(new ByteArrayOutputStream());
PdfDocument documentToMerge = new PdfDocument(
new PdfReader(
new RandomAccessSourceFactory().createSource(baos.toByteArray()),
new ReaderProperties()));
ByteArrayOutputStream resultantBaos = new ByteArrayOutputStream();
PdfDocument resultantDocument = new PdfDocument(new PdfWriter(resultantBaos))) {
// We do expect that the following line will not throw any NPE
PdfPage copiedPage = documentToMerge.getPage(1).copyTo(resultantDocument);
Assertions.assertEquals(1, copiedPage.getAnnotations().size());
Assertions.assertNull(copiedPage.getAnnotations().get(0).getSubtype());
resultantDocument.addPage(copiedPage);
}
}
@Test
public void readPagesInBlocksTest() throws IOException {
String srcFile = SOURCE_FOLDER + "docWithBalancedPageTree.pdf";
int maxAmountOfPagesReadAtATime = 0;
CustomPdfReader reader = new CustomPdfReader(srcFile);
PdfDocument document = new PdfDocument(reader);
for (int page = 1; page <= document.getNumberOfPages(); page++) {
document.getPage(page);
if (reader.numOfPagesRead > maxAmountOfPagesReadAtATime) {
maxAmountOfPagesReadAtATime = reader.numOfPagesRead;
}
reader.numOfPagesRead = 0;
}
Assertions.assertEquals(111, document.getNumberOfPages());
Assertions.assertEquals(10, maxAmountOfPagesReadAtATime);
document.close();
}
@Test
public void readSinglePageTest() throws IOException {
String srcFile = SOURCE_FOLDER + "allPagesAreLeaves.pdf";
CustomPdfReader reader = new CustomPdfReader(srcFile);
reader.setMemorySavingMode(true);
PdfDocument document = new PdfDocument(reader);
int amountOfPages = document.getNumberOfPages();
PdfPages pdfPages = document.catalog.getPageTree().getRoot();
PdfArray pageIndRefArray = ((PdfDictionary) pdfPages.getPdfObject()).getAsArray(PdfName.Kids);
document.getPage(amountOfPages);
Assertions.assertEquals(1, getAmountOfReadPages(pageIndRefArray));
document.getPage(amountOfPages / 2);
Assertions.assertEquals(2, getAmountOfReadPages(pageIndRefArray));
document.getPage(1);
Assertions.assertEquals(3, getAmountOfReadPages(pageIndRefArray));
document.close();
}
@Test
public void implicitPagesTreeRebuildingTest() throws IOException, InterruptedException {
String inFileName = SOURCE_FOLDER + "implicitPagesTreeRebuilding.pdf";
String outFileName = DESTINATION_FOLDER + "implicitPagesTreeRebuilding.pdf";
String cmpFileName = SOURCE_FOLDER + "cmp_implicitPagesTreeRebuilding.pdf";
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inFileName), CompareTool.createTestPdfWriter(outFileName));
pdfDocument.close();
Assertions.assertNull(new CompareTool().compareByContent(outFileName,cmpFileName, DESTINATION_FOLDER));
}
@Test
@LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.PAGE_TREE_IS_BROKEN_FAILED_TO_RETRIEVE_PAGE)})
public void brokenPageTreeWithExcessiveLastPageTest() throws IOException {
String inFileName = SOURCE_FOLDER + "brokenPageTreeNullLast.pdf";
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inFileName));
List<Integer> pages = Collections.singletonList(4);
Set<Integer> nullPages = new HashSet<>(pages);
findAndAssertNullPages(pdfDocument, nullPages);
}
@Test
@LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.PAGE_TREE_IS_BROKEN_FAILED_TO_RETRIEVE_PAGE)})
public void brokenPageTreeWithExcessiveMiddlePageTest() throws IOException {
String inFileName = SOURCE_FOLDER + "brokenPageTreeNullMiddle.pdf";
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inFileName));
List<Integer> pages = Collections.singletonList(3);
Set<Integer> nullPages = new HashSet<>(pages);
findAndAssertNullPages(pdfDocument, nullPages);
}
@Test
@LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.PAGE_TREE_IS_BROKEN_FAILED_TO_RETRIEVE_PAGE, count = 7)})
public void brokenPageTreeWithExcessiveMultipleNegativePagesTest() throws IOException {
String inFileName = SOURCE_FOLDER + "brokenPageTreeNullMultipleSequence.pdf";
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inFileName));
List<Integer> pages = Arrays.asList(2, 3, 4, 6, 7, 8, 9);
Set<Integer> nullPages = new HashSet<>(pages);
findAndAssertNullPages(pdfDocument, nullPages);
}
@Test
@LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.PAGE_TREE_IS_BROKEN_FAILED_TO_RETRIEVE_PAGE, count = 2)})
public void brokenPageTreeWithExcessiveRangeNegativePagesTest() throws IOException {
String inFileName = SOURCE_FOLDER + "brokenPageTreeNullRangeNegative.pdf";
PdfDocument pdfDocument = new PdfDocument(new PdfReader(inFileName));
List<Integer> pages = Arrays.asList(2, 4);
Set<Integer> nullPages = new HashSet<>(pages);
findAndAssertNullPages(pdfDocument, nullPages);
}
@Test
public void testPageTreeGenerationWhenFirstPdfPagesHasOnePageOnly() {
PdfDocument pdfDocument = new PdfDocument(new PdfWriter(new ByteArrayOutputStream()));
int totalPageCount = PdfPagesTree.DEFAULT_LEAF_SIZE + 4;
for (int i = 0; i < totalPageCount; i++) {
pdfDocument.addNewPage();
}
Assertions.assertEquals(2, pdfDocument.getCatalog().getPageTree().getParents().size());
Assertions.assertEquals(PdfPagesTree.DEFAULT_LEAF_SIZE,
pdfDocument.getCatalog().getPageTree().getParents().get(0).getCount());
// Leave only one page in the first pages tree
for (int i = PdfPagesTree.DEFAULT_LEAF_SIZE - 1; i >= 1; i--) {
pdfDocument.removePage(i);
}
Assertions.assertEquals(2, pdfDocument.getCatalog().getPageTree().getParents().size());
Assertions.assertEquals(1,
pdfDocument.getCatalog().getPageTree().getParents().get(0).getCount());
// TODO DEVSIX-5575 remove expected exception and add proper assertions
Assertions.assertThrows(NullPointerException.class, () -> pdfDocument.close());
}
private static void findAndAssertNullPages(PdfDocument pdfDocument, Set<Integer> nullPages) {
for (Integer nullPage : nullPages) {
int pageNum = (int)nullPage;
Exception exception = Assertions.assertThrows(PdfException.class,()-> pdfDocument.getPage(pageNum));
Assertions.assertEquals(exception.getMessage() , MessageFormatUtil.format(
IoLogMessageConstant.PAGE_TREE_IS_BROKEN_FAILED_TO_RETRIEVE_PAGE, pageNum));
}
}
private static int getAmountOfReadPages(PdfArray pageIndRefArray) {
int amountOfLoadedPages = 0;
for (int i = 0; i < pageIndRefArray.size(); i++) {
if (((PdfIndirectReference) pageIndRefArray.get(i, false)).refersTo != null) {
amountOfLoadedPages++;
}
}
return amountOfLoadedPages;
}
private static void verifyPagesOrder(String filename, int numOfPages) throws IOException {
PdfReader reader = CompareTool.createOutputReader(filename);
PdfDocument pdfDocument = new PdfDocument(reader);
Assertions.assertEquals(Boolean.FALSE, reader.hasRebuiltXref(), "Rebuilt");
for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) {
PdfDictionary page = pdfDocument.getPage(i).getPdfObject();
Assertions.assertNotNull(page);
PdfNumber number = page.getAsNumber(PageNum);
Assertions.assertEquals(i, number.intValue(), "Page number");
}
Assertions.assertEquals(numOfPages, pdfDocument.getNumberOfPages(), "Number of pages");
pdfDocument.close();
}
private static int verifyIntegrity(PdfPagesTree pagesTree) {
List<PdfPages> parents = pagesTree.getParents();
int from = 0;
for (int i = 0; i < parents.size(); i++) {
if (parents.get(i).getFrom() != from) {
return i;
}
from = parents.get(i).getFrom() + parents.get(i).getCount();
}
return -1;
}
private static ByteArrayOutputStream createSourceDocumentWithEmptyAnnotation(ByteArrayOutputStream baos) {
try (PdfDocument sourceDocument = new PdfDocument(new PdfWriter(baos))) {
PdfPage page = sourceDocument.addNewPage();
PdfAnnotation annotation = PdfAnnotation.makeAnnotation(new PdfDictionary());
page.addAnnotation(annotation);
return baos;
}
}
private class CustomPdfReader extends PdfReader {
public boolean pagesAreRead = false;
public int numOfPagesRead = 0;
public CustomPdfReader(String filename) throws IOException {
super(filename);
}
@Override
protected PdfObject readObject(PdfIndirectReference reference) {
PdfObject toReturn = super.readObject(reference);
if (toReturn instanceof PdfDictionary
&& PdfName.Page.equals(((PdfDictionary) toReturn).get(PdfName.Type))) {
numOfPagesRead++;
pagesAreRead = true;
}
return toReturn;
}
}
}