PdfStructElemTest.java

/*
    This file is part of the iText (R) project.
    Copyright (c) 1998-2025 Apryse Group NV
    Authors: Apryse Software.

    This program is offered under a commercial and under the AGPL license.
    For commercial licensing, contact us at https://itextpdf.com/sales.  For AGPL licensing, see below.

    AGPL licensing:
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */
package com.itextpdf.kernel.pdf;

import com.itextpdf.io.font.constants.StandardFonts;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.exceptions.KernelExceptionMessageConstant;
import com.itextpdf.kernel.exceptions.PdfException;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.annot.PdfAnnotation;
import com.itextpdf.kernel.pdf.annot.PdfLinkAnnotation;
import com.itextpdf.kernel.pdf.canvas.CanvasTag;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.tagging.PdfMcrDictionary;
import com.itextpdf.kernel.pdf.tagging.PdfMcrNumber;
import com.itextpdf.kernel.pdf.tagging.PdfNamespace;
import com.itextpdf.kernel.pdf.tagging.PdfObjRef;
import com.itextpdf.kernel.pdf.tagging.PdfStructElem;
import com.itextpdf.kernel.pdf.tagging.PdfStructTreeRoot;
import com.itextpdf.kernel.pdf.tagging.StandardRoles;
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 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 org.xml.sax.SAXException;

import javax.xml.parsers.ParserConfigurationException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

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

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

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

    @AfterAll
    public static void afterClass() {
        CompareTool.cleanup(destinationFolder);
    }
    
    @Test
    public void structElemTest01() throws Exception {
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest01.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();
        PdfStructElem doc = document.getStructTreeRoot().addKid(new PdfStructElem(document, PdfName.Document));

        PdfPage page1 = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(page1);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page1));

        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page1, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrDictionary(page1, span1))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();

        PdfPage page2 = document.addNewPage();
        canvas = new PdfCanvas(page2);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page2));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page2, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        PdfStructElem span2 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page2));
        canvas.openTag(new CanvasTag(span2.addKid(new PdfMcrNumber(page2, span2))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();

        page1.flush();
        page2.flush();

        document.close();

        compareResult("structElemTest01.pdf", "cmp_structElemTest01.pdf");
    }

    @Test
    public void structElemTest02() throws Exception {
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest02.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();
        document.getStructTreeRoot().getRoleMap().put(new PdfName("Chunk"), PdfName.Span);
        PdfStructElem doc = document.getStructTreeRoot().addKid(new PdfStructElem(document, PdfName.Document));

        PdfPage page = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(page);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        PdfStructElem span2 = paragraph.addKid(new PdfStructElem(document, new PdfName("Chunk"), page));
        canvas.openTag(new CanvasTag(span2.addKid(new PdfMcrNumber(page, span2))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();
        page.flush();

        document.close();

        compareResult("structElemTest02.pdf", "cmp_structElemTest02.pdf");
    }

    @Test
    public void structElemTest03() throws Exception {
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest03.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();
        document.getStructTreeRoot().getRoleMap().put(new PdfName("Chunk"), PdfName.Span);
        PdfStructElem doc = document.getStructTreeRoot().addKid(new PdfStructElem(document, PdfName.Document));

        PdfPage page1 = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(page1);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page1));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page1, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        PdfStructElem span2 = paragraph.addKid(new PdfStructElem(document, new PdfName("Chunk"), page1));
        canvas.openTag(new CanvasTag(span2.addKid(new PdfMcrNumber(page1, span2))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();

        PdfPage page2 = document.addNewPage();
        canvas = new PdfCanvas(page2);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.HELVETICA), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page2));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page2, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        span2 = paragraph.addKid(new PdfStructElem(document, new PdfName("Chunk"), page2));
        canvas.openTag(new CanvasTag(span2.addKid(new PdfMcrNumber(page2, span2))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();
        page1.flush();
        page2.flush();

        document.close();

        document = new PdfDocument(CompareTool.createOutputReader(destinationFolder + "structElemTest03.pdf"));
        Assertions.assertEquals(2, (int) document.getNextStructParentIndex());
        PdfPage page = document.getPage(1);
        Assertions.assertEquals(0, page.getStructParentIndex());
        Assertions.assertEquals(2, page.getNextMcid());
        document.close();
    }

    @Test
    public void structElemTest04() throws Exception {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfWriter writer = new PdfWriter(baos);
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();
        document.getStructTreeRoot().getRoleMap().put(new PdfName("Chunk"), PdfName.Span);
        PdfStructElem doc = document.getStructTreeRoot().addKid(new PdfStructElem(document, PdfName.Document));

        PdfPage page = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(page);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        PdfStructElem span2 = paragraph.addKid(new PdfStructElem(document, new PdfName("Chunk"), page));
        canvas.openTag(new CanvasTag(span2.addKid(new PdfMcrNumber(page, span2))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();
        page.flush();

        document.close();
        byte[] bytes = baos.toByteArray();

        PdfReader reader = new PdfReader(new ByteArrayInputStream(bytes));
        writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest04.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        document = new PdfDocument(reader, writer);

        page = document.getPage(1);
        canvas = new PdfCanvas(page);

        PdfStructElem p = (PdfStructElem) document.getStructTreeRoot().getKids().get(0).getKids().get(0);

        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 490);

        //Inserting span between of 2 existing ones.
        span1 = p.addKid(1, new PdfStructElem(document, PdfName.Span, page));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page, span1))));
        canvas.showText("text1");
        canvas.closeTag();

        //Inserting span at the end.
        span1 = p.addKid(new PdfStructElem(document, PdfName.Span, page));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page, span1))));
        canvas.showText("text2");
        canvas.closeTag();

        canvas.endText();

        canvas.release();
        page.flush();

        document.close();

        compareResult("structElemTest04.pdf", "cmp_structElemTest04.pdf");
    }

    @Test
    public void structElemTest05() throws Exception {
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest05.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();
        PdfStructElem doc = document.getStructTreeRoot().addKid(new PdfStructElem(document, PdfName.Document));

        PdfPage page = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(page);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 14);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page, span1))));
        canvas.showText("Click ");
        canvas.closeTag();

        PdfStructElem link = paragraph.addKid(new PdfStructElem(document, PdfName.Link, page));
        canvas.openTag(new CanvasTag(link.addKid(new PdfMcrNumber(page, link))));
        canvas.setFillColorRgb(0, 0, 1).showText("here");
        PdfLinkAnnotation linkAnnotation = new PdfLinkAnnotation(new Rectangle(80, 508, 40, 18));
        linkAnnotation.setColor(new float[] {0, 0, 1}).setBorder(new PdfArray(new float[] {0, 0, 1}));
        page.addAnnotation(-1, linkAnnotation, false);
        link.addKid(new PdfObjRef(linkAnnotation, link, document.getNextStructParentIndex()));
        canvas.closeTag();

        PdfStructElem span2 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page));
        canvas.openTag(new CanvasTag(span2.addKid(new PdfMcrNumber(page, span2))));
        canvas.setFillColorRgb(0, 0, 0);
        canvas.showText(" to visit iText site.");
        canvas.closeTag();
        canvas.endText();
        canvas.release();

        document.close();

        compareResult("structElemTest05.pdf", "cmp_structElemTest05.pdf");
    }

    @Test
    public void structElemTest06() throws Exception {
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest06.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();
        PdfStructElem doc = document.getStructTreeRoot().addKid(new PdfStructElem(document, PdfName.Document));

        PdfPage page = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(page);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 14);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page, span1)))
                .addProperty(PdfName.Lang, new PdfString("en-US"))
                .addProperty(PdfName.ActualText, new PdfString("The actual text is: Text with property list")));
        canvas.showText("Text with property list");
        canvas.closeTag();

        canvas.endText();
        canvas.release();

        document.close();

        compareResult("structElemTest06.pdf", "cmp_structElemTest06.pdf");
    }

    @Test
    @LogMessages(messages =
    @LogMessage(messageTemplate = IoLogMessageConstant.VERSION_INCOMPATIBILITY_FOR_DICTIONARY_ENTRY, count = 5))
    public void structElemTest07() throws Exception {
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest07.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();
        PdfStructElem doc = document.getStructTreeRoot().addKid(new PdfStructElem(document, PdfName.Document));

        PdfPage page = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(page);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        PdfStructElem span2 = paragraph.addKid(new PdfStructElem(document, new PdfName("Chunk"), page));
        canvas.openTag(new CanvasTag(span2.addKid(new PdfMcrNumber(page, span2))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();

        PdfNamespace namespace = new PdfNamespace("http://www.w3.org/1999/xhtml");
        span1.setNamespace(namespace);
        span1.addRef(span2);
        span1.setPhoneticAlphabet(PdfName.ipa);
        span1.setPhoneme(new PdfString("Heeeelllloooooo"));
        namespace.addNamespaceRoleMapping(StandardRoles.SPAN, StandardRoles.SPAN);
        document.getStructTreeRoot().addNamespace(namespace);

        page.flush();

        document.close();

        compareResult("structElemTest07.pdf", "cmp_structElemTest07.pdf");
    }

    @Test
    public void structElemTest08() throws Exception {
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest08.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();

        PdfStructTreeRoot doc = document.getStructTreeRoot();

        PdfPage firstPage = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(firstPage);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, firstPage));
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(firstPage, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrDictionary(firstPage, span1))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();

        PdfPage secondPage = document.addNewPage();

        // on flushing, the Document tag is not added
        firstPage.flush();
        secondPage.flush();

        document.close();

        compareResult("structElemTest08.pdf", "cmp_structElemTest08.pdf");
    }

    @Test
    public void structElemTest09() throws Exception {
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structElemTest09.pdf");
        writer.setCompressionLevel(CompressionConstants.NO_COMPRESSION);
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "88th_Academy_Awards_mult_roots.pdf"),
                writer);

        document.removePage(1);
        document.close();

        compareResult("structElemTest09.pdf", "cmp_structElemTest09.pdf");
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
    })
    public void structTreeCopyingTest01() throws Exception {
        PdfDocument source = new PdfDocument(new PdfReader(sourceFolder + "iphone_user_guide.pdf"));

        PdfDocument destination = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest01.pdf"));
        destination.setTagged();
        destination.initializeOutlines();

        ArrayList<Integer> pagesToCopy = new ArrayList<Integer>();
        pagesToCopy.add(3);
        pagesToCopy.add(4);
        pagesToCopy.add(10);
        pagesToCopy.add(11);
        source.copyPagesTo(pagesToCopy, destination);
        source.copyPagesTo(50, 52, destination);

        destination.close();
        source.close();

        compareResult("structTreeCopyingTest01.pdf", "cmp_structTreeCopyingTest01.pdf");
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
    })
    public void structTreeCopyingTest02() throws Exception {
        PdfDocument source = new PdfDocument(new PdfReader(sourceFolder + "iphone_user_guide.pdf"));

        PdfDocument destination = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest02.pdf"));
        destination.setTagged();
        destination.initializeOutlines();

        source.copyPagesTo(6, source.getNumberOfPages(), destination);
        source.copyPagesTo(1, 5, destination);

        destination.close();
        source.close();

        compareResult("structTreeCopyingTest02.pdf", "cmp_structTreeCopyingTest02.pdf");
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
    })
    public void structTreeCopyingTest03() throws Exception {
        PdfDocument source = new PdfDocument(new PdfReader(sourceFolder + "iphone_user_guide.pdf"));

        PdfDocument destination = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest03.pdf"));
        destination.initializeOutlines();

        source.copyPagesTo(6, source.getNumberOfPages(), destination);
        source.copyPagesTo(1, 5, destination);

        destination.close();
        source.close();

        // we don't compare tag structures, because resultant document is not tagged
        Assertions.assertNull(new CompareTool()
                .compareByContent(destinationFolder + "structTreeCopyingTest03.pdf",
                        sourceFolder + "cmp_structTreeCopyingTest03.pdf",
                        destinationFolder, "diff_copying_03_"));
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
    })
    public void structTreeCopyingTest04() throws Exception {
        PdfDocument source = new PdfDocument(new PdfReader(sourceFolder + "iphone_user_guide.pdf"));

        PdfDocument destination = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest04.pdf"));
        destination.setTagged();
        destination.initializeOutlines();

        for (int i = 1; i <= source.getNumberOfPages(); i++) {
            source.copyPagesTo(i, i, destination);
        }

        destination.close();
        source.close();

        compareResult("structTreeCopyingTest04.pdf", "cmp_structTreeCopyingTest04.pdf");
    }

    @Test
    public void structTreeCopyingTest05() throws Exception {
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "iphone_user_guide.pdf"),
                CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest05.pdf"));

        PdfDocument document1 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox.pdf"));
        document1.copyPagesTo(1, 1, document, 2);

        PdfDocument document2 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox-table.pdf"));
        document2.copyPagesTo(1, 3, document, 4);

        document.close();
        document1.close();
        document2.close();

        compareResult("structTreeCopyingTest05.pdf", "cmp_structTreeCopyingTest05.pdf");
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
    })
    public void structTreeCopyingTest06() throws Exception {
        PdfDocument source = new PdfDocument(new PdfReader(sourceFolder + "iphone_user_guide.pdf"));

        PdfDocument destination = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest06.pdf"));
        destination.setTagged();
        destination.initializeOutlines();

        source.copyPagesTo(1, source.getNumberOfPages(), destination);

        destination.close();
        source.close();

        compareResult("structTreeCopyingTest06.pdf", "cmp_structTreeCopyingTest06.pdf");
    }

    @Test
    public void structTreeCopyingTest07() throws Exception {
        PdfReader reader = new PdfReader(sourceFolder + "quick-brown-fox.pdf");
        PdfWriter writer = CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest07.pdf");
        PdfDocument document = new PdfDocument(writer);
        document.setTagged();
        PdfStructElem doc = document.getStructTreeRoot().addKid(new PdfStructElem(document, PdfName.Document));

        PdfPage page1 = document.addNewPage();
        PdfCanvas canvas = new PdfCanvas(page1);
        canvas.beginText();
        canvas.setFontAndSize(PdfFontFactory.createFont(StandardFonts.COURIER), 24);
        canvas.setTextMatrix(1, 0, 0, 1, 32, 512);
        PdfStructElem paragraph = doc.addKid(new PdfStructElem(document, PdfName.P));
        PdfStructElem span1 = paragraph.addKid(new PdfStructElem(document, PdfName.Span, page1));

        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrNumber(page1, span1))));
        canvas.showText("Hello ");
        canvas.closeTag();
        canvas.openTag(new CanvasTag(span1.addKid(new PdfMcrDictionary(page1, span1))));
        canvas.showText("World");
        canvas.closeTag();
        canvas.endText();
        canvas.release();

        PdfDocument document1 = new PdfDocument(reader);
        document1.initializeOutlines();
        document1.copyPagesTo(1, 1, document);

        document.close();
        document1.close();

        compareResult("structTreeCopyingTest07.pdf", "cmp_structTreeCopyingTest07.pdf");
    }

    @Test
    public void structTreeCopyingTest08() throws Exception {
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox-table.pdf"),
                CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest08.pdf"));

        PdfDocument document1 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox.pdf"));
        document1.initializeOutlines();
        document1.copyPagesTo(1, 1, document, 2);

        document.close();
        document1.close();

        compareResult("structTreeCopyingTest08.pdf", "cmp_structTreeCopyingTest08.pdf");
    }

    @Test
    public void structTreeCopyingTest09() throws Exception {
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox-table.pdf"),
                CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest09.pdf"));

        PdfDocument document1 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox.pdf"));
        document1.initializeOutlines();
        document1.copyPagesTo(1, 1, document, 2);
        document1.copyPagesTo(1, 1, document, 4);

        document.close();
        document1.close();

        compareResult("structTreeCopyingTest09.pdf", "cmp_structTreeCopyingTest09.pdf");
    }

    @Test
    public void structTreeCopyingTest10() throws Exception {
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "88th_Academy_Awards.pdf"),
                CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest10.pdf"));

        PdfDocument document1 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox-table.pdf"));
        document1.initializeOutlines();
        document1.copyPagesTo(1, 3, document, 2);

        PdfDocument document2 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox.pdf"));
        document2.initializeOutlines();
        document2.copyPagesTo(1, 1, document, 4);

        document.close();
        document1.close();
        document2.close();

        compareResult("structTreeCopyingTest10.pdf", "cmp_structTreeCopyingTest10.pdf");
    }

    @Test
    @LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.ROLE_MAPPING_FROM_SOURCE_IS_NOT_COPIED_ALREADY_EXIST))
    public void structTreeCopyingTest11() throws Exception {
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "88th_Academy_Awards.pdf"),
                CompareTool.createTestPdfWriter(destinationFolder + "structTreeCopyingTest11.pdf"));

        PdfDocument document1 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox_mapping_mod.pdf"));
        document1.initializeOutlines();
        document1.copyPagesTo(1, 1, document, 2);

        PdfDocument document2 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox.pdf"));
        document2.initializeOutlines();
        document2.copyPagesTo(1, 1, document, 4);

        document.close();
        document1.close();
        document2.close();

        compareResult("structTreeCopyingTest11.pdf", "cmp_structTreeCopyingTest11.pdf");
    }

    @Test
    public void structTreeCopyingToPartiallyFlushedDocumentTest() throws Exception {
        String outFile = "structTreeCopyingToPartiallyFlushedDocumentTest.pdf";

        PdfDocument resultDoc = new PdfDocument(CompareTool.createTestPdfWriter(destinationFolder + outFile));
        resultDoc.setTagged();

        PdfDocument document1 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox.pdf"));
        document1.copyPagesTo(1, 1, resultDoc);
        resultDoc.flushCopiedObjects(document1);
        document1.close();

        PdfDocument document2 = new PdfDocument(new PdfReader(sourceFolder + "quick-brown-fox.pdf"));
        Exception e = Assertions.assertThrows(PdfException.class, () -> {
            document2.copyPagesTo(1, 1, resultDoc);
        });
        // TODO DEVSIX-7005 after exception is gone add assertion for the resulting document
        Assertions.assertEquals(
                KernelExceptionMessageConstant.TAG_STRUCTURE_COPYING_FAILED_IT_MIGHT_BE_CORRUPTED_IN_ONE_OF_THE_DOCUMENTS,
                e.getMessage()
        );
        document2.close();

        resultDoc.close();
    }

    @Test
    @LogMessages(messages = {
            @LogMessage(messageTemplate = IoLogMessageConstant.ENCOUNTERED_INVALID_MCR)
    })
    public void corruptedTagStructureTest01() throws IOException {
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "invalidMcr.pdf"));
        assertTrue(document.isTagged());
        document.close();
    }

    @Test
    public void corruptedTagStructureTest02() throws IOException {
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "directStructElem01.pdf"));
        assertTrue(document.isTagged());
        document.close();
    }

    @Test
    public void corruptedTagStructureTest03() throws IOException {
        PdfReader reader = new PdfReader(sourceFolder + "directStructElem02.pdf");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfWriter writer = new PdfWriter(baos);
        PdfDocument document = new PdfDocument(reader, writer);
        assertTrue(document.isTagged());
        document.close();

        document = new PdfDocument(new PdfReader(new ByteArrayInputStream(baos.toByteArray())));
        assertTrue(document.isTagged());
        document.close();
    }

    @Test
    public void corruptedTagStructureTest04() throws IOException {
        PdfDocument document = new PdfDocument(new PdfReader(sourceFolder + "directStructElem03.pdf"));
        assertTrue(document.isTagged());
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        PdfDocument docToCopyTo = new PdfDocument(new PdfWriter(baos));
        docToCopyTo.setTagged();
        document.copyPagesTo(1, 1, docToCopyTo);
        document.close();

        docToCopyTo.close();

        document = new PdfDocument(new PdfReader(new ByteArrayInputStream(baos.toByteArray())));
        Assertions.assertTrue(document.isTagged());
        document.close();
    }

    @Test
    public void addAnnotationTaggedAsArtifactTest() throws Exception {
        try (PdfDocument document = new PdfDocument(
                CompareTool.createTestPdfWriter(destinationFolder + "addAnnotationTaggedAsArtifact.pdf",
                        new WriterProperties().setPdfVersion(PdfVersion.PDF_2_0)
                                .addPdfUaXmpMetadata(PdfUAConformance.PDF_UA_2)))) {
            document.setTagged();

            PdfPage page = document.addNewPage();
            PdfLinkAnnotation linkAnnotation = new PdfLinkAnnotation(new Rectangle(80, 508, 40, 18));
            linkAnnotation.setFlag(PdfAnnotation.INVISIBLE);
            page.addAnnotation(linkAnnotation);
        }

        compareResult("addAnnotationTaggedAsArtifact.pdf", "cmp_addAnnotationTaggedAsArtifact.pdf");
    }

    @Test
    public void addNotTaggedAnnotationTest() throws Exception {
        try (PdfDocument document = new PdfDocument(
                CompareTool.createTestPdfWriter(destinationFolder + "addNotTaggedAnnotation.pdf",
                        new WriterProperties().setPdfVersion(PdfVersion.PDF_1_7)
                                .addPdfUaXmpMetadata(PdfUAConformance.PDF_UA_2)))) {
            document.setTagged();

            PdfPage page = document.addNewPage();
            PdfLinkAnnotation linkAnnotation = new PdfLinkAnnotation(new Rectangle(80, 508, 40, 18));
            linkAnnotation.setFlag(PdfAnnotation.INVISIBLE);
            page.addAnnotation(linkAnnotation);
        }

        compareResult("addNotTaggedAnnotation.pdf", "cmp_addNotTaggedAnnotation.pdf");
    }

    private void compareResult(String outFileName, String cmpFileName)
            throws IOException, InterruptedException, ParserConfigurationException, SAXException {
        CompareTool compareTool = new CompareTool();
        String outPdf = destinationFolder + outFileName;
        String cmpPdf = sourceFolder + cmpFileName;

        String contentDifferences = compareTool.compareByContent(outPdf,
                cmpPdf, destinationFolder);
        String taggedStructureDifferences = compareTool.compareTagStructures(outPdf, cmpPdf);

        String errorMessage = "";
        errorMessage += taggedStructureDifferences == null ? "" : taggedStructureDifferences + "\n";
        errorMessage += contentDifferences == null ? "" : contentDifferences;
        if (errorMessage.length() > 0) {
            fail(errorMessage);
        }
    }
}