XrefStreamDocumentUpdatesTest.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.kernel.geom.Rectangle;
import com.itextpdf.kernel.logs.KernelLogMessageConstant;
import com.itextpdf.kernel.pdf.annot.PdfTextAnnotation;
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 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("BouncyCastleIntegrationTest")
public class XrefStreamDocumentUpdatesTest extends ExtendedITextTest {
public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/pdf/XrefStreamDocumentUpdatesTest/";
public static final String destinationFolder = TestUtil.getOutputPath() + "/kernel/pdf/XrefStreamDocumentUpdatesTest/";
@BeforeAll
public static void beforeClass() {
createDestinationFolder(destinationFolder);
}
@AfterAll
public static void afterClass() {
CompareTool.cleanup(destinationFolder);
}
@Test
public void readFreeRefReusingInIncrementTest() throws IOException {
PdfDocument document = new PdfDocument(new PdfReader
(sourceFolder + "readFreeRefReusingInIncrement.pdf"));
PdfArray array = (PdfArray) document.getCatalog().getPdfObject()
.get(new PdfName("CustomKey"));
Assertions.assertTrue(array instanceof PdfArray);
Assertions.assertEquals(0, array.size());
}
@Test
public void notReuseIndirectRefForObjectStreamTest() throws IOException {
String inputFile = sourceFolder + "notReuseIndirectRefForObjectStream.pdf";
String outputFile = destinationFolder + "adjustingsInObjStm.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfReader(inputFile),
CompareTool.createTestPdfWriter(outputFile).setCompressionLevel(CompressionConstants.NO_COMPRESSION));
PdfArray media = pdfDoc.getPage(1).getPdfObject().getAsArray(PdfName.MediaBox);
media.remove(2);
media.add(new PdfNumber(500));
media.setModified();
pdfDoc.close();
PdfDocument doc = new PdfDocument(new PdfReader(sourceFolder + "adjustingsInObjStm.pdf"));
PdfObject object = doc.getPdfObject(8);
PdfDictionary pageDict = (PdfDictionary) object;
int expectNumberOfObjects = pdfDoc.getNumberOfPdfObjects();
//output pdf document should be openable
Assertions.assertEquals(10, expectNumberOfObjects);
Assertions.assertEquals(PdfName.ObjStm, pageDict.get(PdfName.Type));
}
@Test
public void notReuseIndRefForObjStreamInIncrementTest() throws IOException {
String inputFile = sourceFolder + "notReuseIndirectRefForObjectStream.pdf";
String outputFile = destinationFolder + "adjustingsInObjStmInIncrement.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfReader(inputFile),
CompareTool.createTestPdfWriter(outputFile).setCompressionLevel(CompressionConstants.NO_COMPRESSION),
new StampingProperties().useAppendMode());
PdfObject newObj = pdfDoc.getPage(1).getPdfObject();
newObj.setModified();
pdfDoc.close();
PdfDocument doc = new PdfDocument(new PdfReader(sourceFolder + "adjustingsInObjStmInIncrement.pdf"));
PdfDictionary objStmDict = (PdfDictionary) doc.getPdfObject(8);
int expectNumberOfObjects = doc.getNumberOfPdfObjects();
//output pdf document should be openable
Assertions.assertEquals(9, expectNumberOfObjects);
Assertions.assertEquals(PdfName.ObjStm, objStmDict.get(PdfName.Type));
doc.close();
}
@Test
public void freeRefReuseWhenAddNewObjTest() throws IOException {
String filename = destinationFolder + "freeRefReuseWhenAddNewObj.pdf";
PdfDocument pdfDoc1 = new PdfDocument(new PdfReader(sourceFolder + "pdfWithRemovedObjInOldVer.pdf"),
CompareTool.createTestPdfWriter(filename).setCompressionLevel(CompressionConstants.NO_COMPRESSION),
new StampingProperties().useAppendMode());
pdfDoc1.getCatalog().getPdfObject().put(new PdfName("CustomKey"), new PdfArray().makeIndirect(pdfDoc1));
PdfObject newObj = pdfDoc1.getCatalog().getPdfObject();
newObj.setModified();
int expectObjNumber = pdfDoc1.getCatalog().getPdfObject().get(new PdfName("CustomKey"))
.getIndirectReference().getObjNumber();
int expectGenNumber = pdfDoc1.getCatalog().getPdfObject().get(new PdfName("CustomKey"))
.getIndirectReference().getGenNumber();
PdfXrefTable xref = pdfDoc1.getXref();
Assertions.assertEquals(8, expectObjNumber);
Assertions.assertEquals(0, expectGenNumber);
Assertions.assertTrue(xref.get(5).isFree());
pdfDoc1.close();
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = KernelLogMessageConstant.MD5_IS_NOT_FIPS_COMPLIANT,
ignore = true))
public void checkEncryptionInXrefStmInIncrementsTest() throws IOException, InterruptedException {
String inFileName = sourceFolder + "encryptedDocWithXrefStm.pdf";
String outFileName = destinationFolder + "checkEncryptionInXrefStmInIncrements.pdf";
PdfReader pdfReader = new PdfReader(inFileName).setUnethicalReading(true);
PdfDocument pdfDocument = new PdfDocument(pdfReader, CompareTool.createTestPdfWriter(outFileName),
new StampingProperties().useAppendMode().preserveEncryption());
PdfDictionary xrefStm = (PdfDictionary) pdfDocument.getPdfObject(6);
pdfDocument.close();
Assertions.assertNull(new CompareTool().compareByContent(outFileName, inFileName, destinationFolder));
Assertions.assertEquals(PdfName.XRef, xrefStm.get(PdfName.Type));
}
@Test
public void hybridReferenceInIncrementsTest() throws IOException, InterruptedException {
String inFileName = sourceFolder + "hybridReferenceDocument.pdf";
String outFileName = destinationFolder + "hybridReferenceInIncrements.pdf";
PdfReader pdfReader = new PdfReader(inFileName);
PdfDocument pdfDocument = new PdfDocument(pdfReader, CompareTool.createTestPdfWriter(outFileName),
new StampingProperties().useAppendMode());
pdfDocument.close();
Assertions.assertNull(new CompareTool().compareByContent(outFileName, inFileName, destinationFolder));
}
@Test
public void xrefStmInWriteModeTest() throws IOException {
String fileName = destinationFolder + "xrefStmInWriteMode.pdf";
PdfWriter writer = CompareTool.createTestPdfWriter(fileName, new WriterProperties().setFullCompressionMode(true)
.setCompressionLevel(CompressionConstants.NO_COMPRESSION));
PdfDocument pdfDocument = new PdfDocument(writer);
PdfPage page = pdfDocument.addNewPage();
PdfTextAnnotation textannot = new PdfTextAnnotation(new Rectangle(100, 600, 50, 40));
textannot
.setText(new PdfString("Text Annotation 01"))
.setContents(new PdfString("Some contents..."));
page.addAnnotation(textannot);
pdfDocument.close();
PdfDocument doc = new PdfDocument(CompareTool.createOutputReader(fileName));
int xrefTableCounter = 0;
for (int i = 1; i < doc.getNumberOfPdfObjects(); i++) {
PdfObject obj = doc.getPdfObject(i);
if (obj instanceof PdfDictionary) {
PdfDictionary objStmDict = (PdfDictionary) doc.getPdfObject(i);
PdfObject type = objStmDict.get(PdfName.Type);
if (type != null && type.equals(PdfName.XRef)) {
xrefTableCounter++;
}
}
}
Assertions.assertEquals(((PdfNumber) doc.getTrailer().get(PdfName.Size)).intValue(), doc.getNumberOfPdfObjects());
doc.close();
Assertions.assertEquals(1, xrefTableCounter);
}
@Test
public void xrefStmInAppendModeTest() throws IOException {
String fileName = destinationFolder + "xrefStmInAppendMode.pdf";
PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + "xrefStmInWriteMode.pdf"),
CompareTool.createTestPdfWriter(fileName).setCompressionLevel(CompressionConstants.NO_COMPRESSION),
new StampingProperties().useAppendMode());
pdfDocument.close();
PdfDocument doc = new PdfDocument(CompareTool.createOutputReader(fileName));
int xrefTableCounter = 0;
for (int i = 1; i < doc.getNumberOfPdfObjects(); i++) {
PdfObject obj = doc.getPdfObject(i);
if (obj instanceof PdfDictionary) {
PdfDictionary objStmDict = (PdfDictionary) doc.getPdfObject(i);
PdfObject type = objStmDict.get(PdfName.Type);
if (type != null && type.equals(PdfName.XRef)) {
xrefTableCounter++;
}
}
}
Assertions.assertEquals(((PdfNumber) doc.getTrailer().get(PdfName.Size)).intValue(), doc.getNumberOfPdfObjects());
doc.close();
Assertions.assertEquals(2, xrefTableCounter);
}
@Test
public void closeDocumentWithoutModificationsTest() throws IOException {
String fileName = destinationFolder + "xrefStmInAppendMode.pdf";
PdfDocument pdfDocument = new PdfDocument(new PdfReader(sourceFolder + "xrefStmInWriteMode.pdf"),
CompareTool.createTestPdfWriter(fileName).setCompressionLevel(CompressionConstants.NO_COMPRESSION),
new StampingProperties().useAppendMode());
// Clear state for document info indirect reference so that there are no modified objects
// in the document due to which, the document will have only one xref table.
pdfDocument.getDocumentInfo().getPdfObject().getIndirectReference().clearState(PdfObject.MODIFIED);
pdfDocument.close();
PdfDocument doc = new PdfDocument(CompareTool.createOutputReader(fileName));
int xrefTableCounter = 0;
for (int i = 1; i < doc.getNumberOfPdfObjects(); i++) {
PdfObject obj = doc.getPdfObject(i);
if (obj instanceof PdfDictionary) {
PdfDictionary objStmDict = (PdfDictionary) doc.getPdfObject(i);
PdfObject type = objStmDict.get(PdfName.Type);
if (type != null && type.equals(PdfName.XRef)) {
xrefTableCounter++;
}
}
}
Assertions.assertEquals(((PdfNumber) doc.getTrailer().get(PdfName.Size)).intValue(), doc.getNumberOfPdfObjects());
doc.close();
Assertions.assertEquals(1, xrefTableCounter);
}
@Test
public void hybridReferenceIncrementTwiceTest() throws IOException, InterruptedException {
String inFileName = sourceFolder + "hybridReferenceDocument.pdf";
String outFileName = destinationFolder + "hybridReferenceDocumentUpdateTwice.pdf";
PdfDocument pdfDoc1 = new PdfDocument(
new PdfReader(inFileName),
new PdfWriter(destinationFolder + "hybridReferenceDocumentUpdate.pdf"),
new StampingProperties().useAppendMode()
);
pdfDoc1.close();
PdfDocument pdfDoc2 = new PdfDocument(
new PdfReader(destinationFolder + "hybridReferenceDocumentUpdate.pdf"),
CompareTool.createTestPdfWriter(outFileName),
new StampingProperties().useAppendMode()
);
pdfDoc2.close();
//if document processed correctly, no errors should occur
Assertions.assertNull(new CompareTool().compareByContent(outFileName, inFileName, destinationFolder));
}
}