PdfStampingTest.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.DateTimeUtil;
import com.itextpdf.io.source.ByteArrayOutputStream;
import com.itextpdf.io.source.ByteUtils;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.logs.KernelLogMessageConstant;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.canvas.parser.PdfCanvasProcessor;
import com.itextpdf.kernel.pdf.canvas.parser.listener.LocationTextExtractionStrategy;
import com.itextpdf.kernel.pdf.event.AbstractPdfDocumentEvent;
import com.itextpdf.kernel.pdf.event.AbstractPdfDocumentEventHandler;
import com.itextpdf.kernel.pdf.event.PdfDocumentEvent;
import com.itextpdf.kernel.utils.CompareTool;
import com.itextpdf.kernel.xmp.XMPException;
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.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Calendar;
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;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

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

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

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

    @AfterAll
    public static void afterClass() {
        CompareTool.cleanup(destinationFolder);
    }
    
    @Test
    public void stamping1() throws IOException {
        String filename1 = destinationFolder + "stamping1_1.pdf";
        String filename2 = destinationFolder + "stamping1_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        pdfDoc1.getDocumentInfo().setAuthor("Alexander Chingarev").
                setCreator("iText 6").
                setTitle("Empty iText 6 Document");
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%Hello World\n"));
        page1.flush();
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2);
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        pdfDoc2.getDocumentInfo().setCreator("iText").setTitle("Empty iText Document");
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(destinationFolder + "stamping1_2.pdf");
        PdfDocument document = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        PdfDictionary trailer = document.getTrailer();
        PdfDictionary info = trailer.getAsDictionary(PdfName.Info);
        PdfString creator = info.getAsString(PdfName.Creator);
        assertEquals("iText", creator.toString());
        byte[] bytes = document.getPage(1).getContentBytes();
        assertEquals("%Hello World\n", new String(bytes));
        String date = document.getDocumentInfo().getPdfObject().getAsString(PdfName.ModDate).getValue();
        Calendar cl = PdfDate.decode(date);
        double diff = DateTimeUtil.getUtcMillisFromEpoch(null) - DateTimeUtil.getUtcMillisFromEpoch(cl);
        String message = "Unexpected creation date. Different from now is " + (float) diff / 1000 + "s";
        assertTrue(diff < 5000, message);
        document.close();
    }

    @Test
    public void stamping2() throws IOException {
        String filename1 = destinationFolder + "stamping2_1.pdf";
        String filename2 = destinationFolder + "stamping2_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2);
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        PdfPage page2 = pdfDoc2.addNewPage();
        page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 2\n"));
        page2.flush();
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(destinationFolder + "stamping2_2.pdf");
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        byte[] bytes = pdfDocument.getPage(1).getContentBytes();
        assertEquals("%page 1\n", new String(bytes));
        bytes = pdfDocument.getPage(2).getContentBytes();
        assertEquals("%page 2\n", new String(bytes));
        reader.close();
    }

    @Test
    public void stamping3() throws IOException {
        String filename1 = destinationFolder + "stamping3_1.pdf";
        String filename2 = destinationFolder + "stamping3_2.pdf";

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        PdfPage page2 = pdfDoc2.addNewPage();
        page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 2\n"));
        page2.flush();
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        byte[] bytes = pdfDocument.getPage(1).getContentBytes();
        assertEquals("%page 1\n", new String(bytes));
        bytes = pdfDocument.getPage(2).getContentBytes();
        assertEquals("%page 2\n", new String(bytes));
        pdfDocument.close();
    }

    @Test
    public void stamping4() throws IOException {
        String filename1 = destinationFolder + "stamping4_1.pdf";
        String filename2 = destinationFolder + "stamping4_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        int pageCount = 15;
        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2));
        for (int i = 2; i <= pageCount; i++) {
            PdfPage page2 = pdfDoc2.addNewPage();
            page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page2.flush();
        }
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        assertEquals(pageCount, pdfDocument.getNumberOfPages(), "Page count");
        for (int i = 1; i < pdfDocument.getNumberOfPages(); i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes));
        }
        pdfDocument.close();
    }

    @Test
    public void stamping5() throws IOException {
        String filename1 = destinationFolder + "stamping5_1.pdf";
        String filename2 = destinationFolder + "stamping5_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        int pageCount = 15;
        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        for (int i = 2; i <= pageCount; i++) {
            PdfPage page2 = pdfDoc2.addNewPage();
            page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page2.flush();
        }
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        assertEquals(pageCount, pdfDocument.getNumberOfPages(), "Page count");
        for (int i = 1; i < pdfDocument.getNumberOfPages(); i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes));
        }
        reader.close();
    }

    @Test
    public void stamping6() throws IOException {
        String filename1 = destinationFolder + "stamping6_1.pdf";
        String filename2 = destinationFolder + "stamping6_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(true)));
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2));
        PdfPage page2 = pdfDoc2.addNewPage();
        page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 2\n"));
        page2.flush();
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        byte[] bytes = pdfDocument.getPage(1).getContentBytes();
        assertEquals("%page 1\n", new String(bytes));
        bytes = pdfDocument.getPage(2).getContentBytes();
        assertEquals("%page 2\n", new String(bytes));
        pdfDocument.close();
    }

    @Test
    public void stamping7() throws IOException {
        String filename1 = destinationFolder + "stamping7_1.pdf";
        String filename2 = destinationFolder + "stamping7_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        PdfPage page2 = pdfDoc2.addNewPage();
        page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 2\n"));
        page2.flush();
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        byte[] bytes = pdfDocument.getPage(1).getContentBytes();
        assertEquals("%page 1\n", new String(bytes));
        bytes = pdfDocument.getPage(2).getContentBytes();
        assertEquals("%page 2\n", new String(bytes));
        pdfDocument.close();
    }

    @Test
    public void stamping8() throws IOException {
        String filename1 = destinationFolder + "stamping8_1.pdf";
        String filename2 = destinationFolder + "stamping8_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stamping9() throws IOException {
        String filename1 = destinationFolder + "stamping9_1.pdf";
        String filename2 = destinationFolder + "stamping9_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stamping10() throws IOException {
        String filename1 = destinationFolder + "stamping10_1.pdf";
        String filename2 = destinationFolder + "stamping10_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stamping11() throws IOException {
        String filename1 = destinationFolder + "stamping11_1.pdf";
        String filename2 = destinationFolder + "stamping11_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stamping12() throws IOException {
        String filename1 = destinationFolder + "stamping12_1.pdf";
        String filename2 = destinationFolder + "stamping12_2.pdf";
        int pageCount = 1010;

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2));

        int newPageCount = 10;
        for (int i = pageCount; i > newPageCount; i--) {
            pdfDoc2.removePage(i);
        }
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 1; i <= pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i);
        }
        PdfPage pdfPage = pdfDoc3.getPage(1);
        PdfDictionary root = pdfPage.getPdfObject().getAsDictionary(PdfName.Parent);
        assertEquals(newPageCount, root.getAsArray(PdfName.Kids).size(), "PdfPages kids count");
        assertEquals(newPageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stamping13() throws IOException {
        String filename1 = destinationFolder + "stamping13_1.pdf";
        String filename2 = destinationFolder + "stamping13_2.pdf";
        int pageCount = 1010;

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2));

        for (int i = pageCount; i > 1; i--) {
            pdfDoc2.removePage(i);
        }
        pdfDoc2.removePage(1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc2.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 1; i <= pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i);
        }
        PdfArray rootKids = pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject().getAsArray(PdfName.Kids);
        assertEquals(2, rootKids.size(), "Page root kids count");
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stamping14() throws IOException {
        String filename1 = sourceFolder + "20000PagesDocument.pdf";
        String filename2 = destinationFolder + "stamping14.pdf";

        PdfDocument pdfDoc2 = new PdfDocument(new PdfReader(filename1), CompareTool.createTestPdfWriter(filename2));

        for (int i = pdfDoc2.getNumberOfPages(); i > 3; i--) {
            pdfDoc2.removePage(i);
        }

        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 1; i <= pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i);
        }
        //NOTE: during page removing iText don't flatten page structure (we can end up with a lot of embedded pages dictionaries)
        assertEquals(42226, pdfDoc3.getXref().size(), "Xref size");
        assertEquals(3, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pdfDocument.getNumberOfPages(); i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stampingStreamsCompression01() throws IOException {
        // by default, old streams should not be recompressed

        String filenameIn = sourceFolder + "stampingStreamsCompression.pdf";
        String filenameOut = destinationFolder + "stampingStreamsCompression01.pdf";

        PdfReader reader = new PdfReader(filenameIn);
        PdfWriter writer = CompareTool.createTestPdfWriter(filenameOut);
        writer.setCompressionLevel(CompressionConstants.BEST_COMPRESSION);
        PdfDocument doc = new PdfDocument(reader, writer);
        PdfStream stream = (PdfStream) doc.getPdfObject(6);
        int lengthBefore = stream.getLength();
        doc.close();

        doc = new PdfDocument(CompareTool.createOutputReader(filenameOut));
        stream = (PdfStream) doc.getPdfObject(6);
        int lengthAfter = stream.getLength();

        assertEquals(5731884, lengthBefore);
        float expected = 5731884;
        float coef = Math.abs((expected - lengthAfter) / expected);
        assertTrue(coef < 0.01);
    }

    @Test
    public void stampingStreamsCompression02() throws IOException {
        // if user specified, stream may be uncompressed

        String filenameIn = sourceFolder + "stampingStreamsCompression.pdf";
        String filenameOut = destinationFolder + "stampingStreamsCompression02.pdf";

        PdfReader reader = new PdfReader(filenameIn);
        PdfWriter writer = CompareTool.createTestPdfWriter(filenameOut);
        PdfDocument doc = new PdfDocument(reader, writer);
        PdfStream stream = (PdfStream) doc.getPdfObject(6);
        int lengthBefore = stream.getLength();
        stream.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        doc.close();

        doc = new PdfDocument(CompareTool.createOutputReader(filenameOut));
        stream = (PdfStream) doc.getPdfObject(6);
        int lengthAfter = stream.getLength();

        assertEquals(5731884, lengthBefore);
        float expected = 11321910;
        float coef = Math.abs((expected - lengthAfter) / expected);
        assertTrue(coef < 0.01);
    }

    @Test
    public void stampingStreamsCompression03() throws IOException {
        // if user specified, stream may be recompressed

        String filenameIn = sourceFolder + "stampingStreamsCompression.pdf";
        String filenameOut = destinationFolder + "stampingStreamsCompression03.pdf";

        PdfDocument doc = new PdfDocument(new PdfReader(filenameIn), CompareTool.createTestPdfWriter(filenameOut));
        PdfStream stream = (PdfStream) doc.getPdfObject(6);
        int lengthBefore = stream.getLength();
        stream.setCompressionLevel(CompressionConstants.BEST_COMPRESSION);
        doc.close();

        doc = new PdfDocument(CompareTool.createOutputReader(filenameOut));
        stream = (PdfStream) doc.getPdfObject(6);
        int lengthAfter = stream.getLength();

        assertEquals(5731884, lengthBefore);
        float expected = 5729270;
        float coef = Math.abs((expected - lengthAfter) / expected);
        assertTrue(coef < 0.01);
    }

    @Test
    public void stampingXmp1() throws IOException, XMPException {
        String filename1 = destinationFolder + "stampingXmp1_1.pdf";
        String filename2 = destinationFolder + "stampingXmp1_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2,
                new WriterProperties().setFullCompressionMode(false).addXmpMetadata());
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        pdfDoc2.getDocumentInfo().setAuthor("Alexander Chingarev");
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertNotNull(pdfDoc3.getXmpMetadata(), "XmpMetadata not found");
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stampingXmp2() throws IOException, XMPException {
        String filename1 = destinationFolder + "stampingXmp2_1.pdf";
        String filename2 = destinationFolder + "stampingXmp2_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2,
                new WriterProperties().setFullCompressionMode(true).addXmpMetadata());
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2);
        pdfDoc2.getDocumentInfo().setAuthor("Alexander Chingarev");
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertNotNull(pdfDoc3.getXmpMetadata(), "XmpMetadata not found");
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stampingAppend1() throws IOException {
        String filename1 = destinationFolder + "stampingAppend1_1.pdf";
        String filename2 = destinationFolder + "stampingAppend1_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        pdfDoc1.getDocumentInfo().setAuthor("Alexander Chingarev").
                setCreator("iText 6").
                setTitle("Empty iText 6 Document");
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%Hello World\n"));
        page1.flush();
        pdfDoc1.close();

        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2), new StampingProperties().useAppendMode());
        pdfDoc2.getDocumentInfo().setCreator("iText").setTitle("Empty iText Document");
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        PdfDictionary trailer = pdfDocument.getTrailer();
        PdfDictionary info = trailer.getAsDictionary(PdfName.Info);
        PdfString creator = info.getAsString(PdfName.Creator);
        assertEquals("iText", creator.toString());
        byte[] bytes = pdfDocument.getPage(1).getContentBytes();
        assertEquals("%Hello World\n", new String(bytes));
        String date = pdfDocument.getDocumentInfo().getPdfObject().getAsString(PdfName.ModDate).getValue();
        Calendar cl = PdfDate.decode(date);
        double diff = DateTimeUtil.getUtcMillisFromEpoch(null) - DateTimeUtil.getUtcMillisFromEpoch(cl);
        String message = "Unexpected creation date. Different from now is " + (float) diff / 1000 + "s";
        assertTrue(diff < 5000, message);
        pdfDocument.close();
    }

    @Test
    public void stampingAppend2() throws IOException {
        String filename1 = destinationFolder + "stampingAppend2_1.pdf";
        String filename2 = destinationFolder + "stampingAppend2_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2), new StampingProperties().useAppendMode());
        PdfPage page2 = pdfDoc2.addNewPage();
        page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 2\n"));
        page2.setModified();
        page2.flush();
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        byte[] bytes = pdfDocument.getPage(1).getContentBytes();
        assertEquals("%page 1\n", new String(bytes));
        bytes = pdfDocument.getPage(2).getContentBytes();
        assertEquals("%page 2\n", new String(bytes));
        pdfDocument.close();
    }

    @Test
    public void stampingAppend3() throws IOException {
        String filename1 = destinationFolder + "stampingAppend3_1.pdf";
        String filename2 = destinationFolder + "stampingAppend3_2.pdf";

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2), new StampingProperties().useAppendMode());
        PdfPage page2 = pdfDoc2.addNewPage();
        page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 2\n"));

        page2.flush();
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        byte[] bytes = pdfDocument.getPage(1).getContentBytes();
        assertEquals("%page 1\n", new String(bytes));
        bytes = pdfDocument.getPage(2).getContentBytes();
        assertEquals("%page 2\n", new String(bytes));
        pdfDocument.close();
    }

    @Test
    public void stampingAppend4() throws IOException {
        String filename1 = destinationFolder + "stampingAppend4_1.pdf";
        String filename2 = destinationFolder + "stampingAppend4_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        int pageCount = 15;
        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2), new StampingProperties().useAppendMode());
        for (int i = 2; i <= pageCount; i++) {
            PdfPage page2 = pdfDoc2.addNewPage();
            page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page2.flush();
        }

        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        assertEquals(pageCount, pdfDocument.getNumberOfPages(), "Page count");
        for (int i = 1; i < pdfDocument.getNumberOfPages(); i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes));
        }
        pdfDocument.close();
    }

    @Test
    @LogMessages(messages = {@LogMessage(messageTemplate = KernelLogMessageConstant.FULL_COMPRESSION_APPEND_MODE_XREF_TABLE_INCONSISTENCY)})
    public void stampingAppend5() throws IOException {
        String filename1 = destinationFolder + "stampingAppend5_1.pdf";
        String filename2 = destinationFolder + "stampingAppend5_2.pdf";

        PdfDocument pdfDoc1 = new PdfDocument(CompareTool.createTestPdfWriter(filename1));
        PdfPage page1 = pdfDoc1.addNewPage();
        page1.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page 1\n"));
        page1.flush();
        pdfDoc1.close();

        int pageCount = 15;
        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2, new StampingProperties().useAppendMode());
        for (int i = 2; i <= pageCount; i++) {
            PdfPage page2 = pdfDoc2.addNewPage();
            page2.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page2.flush();
        }
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        assertEquals(pageCount, pdfDocument.getNumberOfPages(), "Page count");
        for (int i = 1; i < pdfDocument.getNumberOfPages(); i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes));
        }
        pdfDocument.close();
    }

    @Test
    public void stampingAppend8() throws IOException {
        String filename1 = destinationFolder + "stampingAppend8_1.pdf";
        String filename2 = destinationFolder + "stampingAppend8_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfDocument pdfDoc2 = new PdfDocument(CompareTool.createOutputReader(filename1), CompareTool.createTestPdfWriter(filename2), new StampingProperties().useAppendMode());
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    @LogMessages(messages = {@LogMessage(messageTemplate = KernelLogMessageConstant.FULL_COMPRESSION_APPEND_MODE_XREF_TABLE_INCONSISTENCY)})
    public void stampingAppend9() throws IOException {
        String filename1 = destinationFolder + "stampingAppend9_1.pdf";
        String filename2 = destinationFolder + "stampingAppend9_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2, new StampingProperties().useAppendMode());
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    @LogMessages(messages = {@LogMessage(messageTemplate = KernelLogMessageConstant.FULL_COMPRESSION_APPEND_MODE_XREF_STREAM_INCONSISTENCY)})
    public void stampingAppend10() throws IOException {
        String filename1 = destinationFolder + "stampingAppend10_1.pdf";
        String filename2 = destinationFolder + "stampingAppend10_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(true));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2, new StampingProperties().useAppendMode());
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stampingAppend11() throws IOException {
        String filename1 = destinationFolder + "stampingAppend11_1.pdf";
        String filename2 = destinationFolder + "stampingAppend11_2.pdf";
        int pageCount = 10;

        PdfWriter writer1 = CompareTool.createTestPdfWriter(filename1, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc1 = new PdfDocument(writer1);
        for (int i = 1; i <= pageCount; i++) {
            PdfPage page = pdfDoc1.addNewPage();
            page.getContentStream(0).getOutputStream().write(ByteUtils.getIsoBytes("%page " + i + "\n"));
            page.flush();
        }
        pdfDoc1.close();

        PdfReader reader2 = CompareTool.createOutputReader(filename1);
        PdfWriter writer2 = CompareTool.createTestPdfWriter(filename2, new WriterProperties().setFullCompressionMode(false));
        PdfDocument pdfDoc2 = new PdfDocument(reader2, writer2, new StampingProperties().useAppendMode());
        pdfDoc2.close();

        PdfReader reader3 = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDoc3 = new PdfDocument(reader3);
        for (int i = 0; i < pdfDoc3.getNumberOfPages(); i++) {
            pdfDoc3.getPage(i + 1);
        }
        assertEquals(pageCount, pdfDoc3.getNumberOfPages(), "Number of pages");
        assertFalse(reader3.hasRebuiltXref(), "Rebuilt");
        assertFalse(reader3.hasFixedXref(), "Fixed");
        verifyPdfPagesCount(pdfDoc3.getCatalog().getPageTree().getRoot().getPdfObject());
        pdfDoc3.close();

        PdfReader reader = CompareTool.createOutputReader(filename2);
        PdfDocument pdfDocument = new PdfDocument(reader);
        assertFalse(reader.hasRebuiltXref(), "Rebuilt");
        for (int i = 1; i <= pageCount; i++) {
            byte[] bytes = pdfDocument.getPage(i).getContentBytes();
            assertEquals("%page " + i + "\n", new String(bytes), "Page content at page " + i);
        }
        pdfDocument.close();
    }

    @Test
    public void stampingVersionTest01() throws IOException {
        // By default the version of the output file should be the same as the original one
        String in = sourceFolder + "hello.pdf";
        String out = destinationFolder + "hello_stamped01.pdf";

        PdfDocument pdfDoc = new PdfDocument(new PdfReader(in), CompareTool.createTestPdfWriter(out));

        assertEquals(PdfVersion.PDF_1_4, pdfDoc.getPdfVersion());

        pdfDoc.close();

        PdfDocument assertPdfDoc = new PdfDocument(CompareTool.createOutputReader(out));
        assertEquals(PdfVersion.PDF_1_4, assertPdfDoc.getPdfVersion());
        assertPdfDoc.close();
    }

    @Test
    public void stampingVersionTest02() throws IOException {
        // There is a possibility to override version in stamping mode
        String in = sourceFolder + "hello.pdf";
        String out = destinationFolder + "hello_stamped02.pdf";

        PdfDocument pdfDoc = new PdfDocument(new PdfReader(in), CompareTool.createTestPdfWriter(out, new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0)));

        assertEquals(PdfVersion.PDF_2_0, pdfDoc.getPdfVersion());

        pdfDoc.close();

        PdfDocument assertPdfDoc = new PdfDocument(CompareTool.createOutputReader(out));
        assertEquals(PdfVersion.PDF_2_0, assertPdfDoc.getPdfVersion());
        assertPdfDoc.close();
    }

    @Test
    public void stampingAppendVersionTest01() throws IOException {
        // There is a possibility to override version in stamping mode
        String in = sourceFolder + "hello.pdf";
        String out = destinationFolder + "stampingAppendVersionTest01.pdf";

        PdfReader reader = new PdfReader(in);
        PdfWriter writer = CompareTool.createTestPdfWriter(out, new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0));
        PdfDocument pdfDoc = new PdfDocument(reader, writer, new StampingProperties().useAppendMode());

        assertEquals(PdfVersion.PDF_2_0, pdfDoc.getPdfVersion());

        pdfDoc.close();

        PdfDocument assertPdfDoc = new PdfDocument(CompareTool.createOutputReader(out));
        assertEquals(PdfVersion.PDF_2_0, assertPdfDoc.getPdfVersion());
        assertPdfDoc.close();
    }

    @Test
    public void stampingTestWithTaggedStructure() throws IOException {
        String filename = sourceFolder + "iphone_user_guide.pdf";

        PdfDocument pdfDoc = new PdfDocument(new PdfReader(filename), CompareTool.createTestPdfWriter(destinationFolder + "stampingDocWithTaggedStructure.pdf"));
        pdfDoc.close();
    }

    @Test
    public void stampingTestWithFullCompression01() throws IOException, InterruptedException {
        String compressedOutPdf = destinationFolder + "stampingTestWithFullCompression01Compressed.pdf";
        String decompressedOutPdf = destinationFolder + "stampingTestWithFullCompression01Decompressed.pdf";
        
        PdfDocument pdfDoc = new PdfDocument(new PdfReader(sourceFolder + "fullCompressedDocument.pdf"),
                new PdfWriter(compressedOutPdf));
        pdfDoc.close();
        float compressedLength = new File(compressedOutPdf).length();

        pdfDoc = new PdfDocument(new PdfReader(sourceFolder + "fullCompressedDocument.pdf"),
                new PdfWriter(decompressedOutPdf, new WriterProperties().setFullCompressionMode(false)));
        pdfDoc.close();
        float decompressedLength = new File(decompressedOutPdf).length();

        float coef = compressedLength / decompressedLength;
        String compareRes = new CompareTool().compareByContent(compressedOutPdf, decompressedOutPdf, destinationFolder);
        assertTrue(coef < 0.7);
        assertNull(compareRes);
    }

    @Test
    //TODO: DEVSIX-2007
    public void stampingStreamNoEndingWhitespace01() throws IOException, InterruptedException {
        PdfDocument pdfDocInput = new PdfDocument(new PdfReader(sourceFolder + "stampingStreamNoEndingWhitespace01.pdf"));
        PdfDocument pdfDocOutput = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "stampingStreamNoEndingWhitespace01.pdf", new WriterProperties().setCompressionLevel(0)));

        pdfDocOutput.addEventHandler(PdfDocumentEvent.END_PAGE, new WatermarkEventHandler());

        pdfDocInput.copyPagesTo(1, pdfDocInput.getNumberOfPages(), pdfDocOutput);

        pdfDocInput.close();
        pdfDocOutput.close();

        Assertions.assertNull(new CompareTool().compareByContent(destinationFolder + "stampingStreamNoEndingWhitespace01.pdf", sourceFolder + "cmp_stampingStreamNoEndingWhitespace01.pdf", destinationFolder, "diff_"));
    }

    @Test
    // with some PDFs, when adding content to an existing PDF in append mode, the resource dictionary didn't get written as a new version
    public void stampingInAppendModeCreatesNewResourceDictionary() throws Exception {
        StampingProperties stampProps = new StampingProperties();
        stampProps.useAppendMode();
        stampProps.preserveEncryption();

        PdfFont font = PdfFontFactory.createFont();

        ByteArrayOutputStream resultStream = new ByteArrayOutputStream();
        PdfDocument pdfDoc = new PdfDocument(new PdfReader(sourceFolder + "hello-d.pdf"), new PdfWriter(resultStream), stampProps);
        PdfPage page = pdfDoc.getPage(1);
        PdfCanvas canvas = new PdfCanvas(page.newContentStreamAfter(), page.getResources(), pdfDoc);
        canvas.beginText();
        canvas.setTextRenderingMode(2);
        canvas.setFontAndSize(font, 42);
        canvas.setTextMatrix(1, 0, 0, -1, 100, 100);
        canvas.showText("TEXT TO STAMP");
        canvas.endText();
        pdfDoc.close();

        // parse text
        pdfDoc = new PdfDocument(new PdfReader(new ByteArrayInputStream(resultStream.toByteArray())));
        LocationTextExtractionStrategy strat = new LocationTextExtractionStrategy();
        PdfCanvasProcessor processor = new PdfCanvasProcessor(strat);
        // this fails with an NPE b/c the /F1 font isn't in the fonts dictionary
        processor.processPageContent(pdfDoc.getPage(1));
        Assertions.assertTrue(strat.getResultantText().contains("TEXT TO STAMP"));

    }

    
    static void verifyPdfPagesCount(PdfObject root) {
        if (root.getType() == PdfObject.INDIRECT_REFERENCE)
            root = ((PdfIndirectReference) root).getRefersTo();
        PdfDictionary pages = (PdfDictionary) root;
        if (!pages.containsKey(PdfName.Kids)) return;
        PdfNumber count = pages.getAsNumber(PdfName.Count);
        if (count != null) {
            assertTrue(count.intValue() > 0, "PdfPages with zero count");
        }
        PdfObject kids = pages.get(PdfName.Kids);
        if (kids.getType() == PdfObject.ARRAY) {
            for (PdfObject kid : (PdfArray) kids) {
                verifyPdfPagesCount(kid);
            }
        } else {
            verifyPdfPagesCount(kids);
        }
    }

    static class WatermarkEventHandler extends AbstractPdfDocumentEventHandler {

        @Override
        public void onAcceptedEvent(AbstractPdfDocumentEvent event) {
            PdfDocumentEvent pdfEvent = (PdfDocumentEvent) event;
            PdfPage page = pdfEvent.getPage();
            PdfCanvas pdfCanvas = new PdfCanvas(page);
            try {
                pdfCanvas.beginText()
                        .setFontAndSize(PdfFontFactory.createFont(), 12.0f)
                        .showText("Text")
                        .endText();
            } catch (IOException e) {
            }
        }
    }
}