PdfMergerTest.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.utils;
import com.itextpdf.io.logs.IoLogMessageConstant;
import com.itextpdf.kernel.logs.KernelLogMessageConstant;
import com.itextpdf.kernel.pdf.PdfDictionary;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfName;
import com.itextpdf.kernel.pdf.PdfReader;
import com.itextpdf.kernel.pdf.PdfWriter;
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.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
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;
@Tag("IntegrationTest")
public class PdfMergerTest extends ExtendedITextTest {
public static final String sourceFolder = "./src/test/resources/com/itextpdf/kernel/utils/PdfMergerTest/";
public static final String destinationFolder = TestUtil.getOutputPath() + "/kernel/utils/PdfMergerTest/";
@BeforeAll
public static void beforeClass() {
createDestinationFolder(destinationFolder);
}
@AfterAll
public static void afterClass() {
CompareTool.cleanup(destinationFolder);
}
@Test
public void mergeDocumentTest01() throws IOException, InterruptedException {
String filename = sourceFolder + "courierTest.pdf";
String filename1 = sourceFolder + "helveticaTest.pdf";
String filename2 = sourceFolder + "timesRomanTest.pdf";
String resultFile = destinationFolder + "mergedResult01.pdf";
PdfReader reader = new PdfReader(filename);
PdfReader reader1 = new PdfReader(filename1);
PdfReader reader2 = new PdfReader(filename2);
PdfWriter writer1 = CompareTool.createTestPdfWriter(resultFile);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfDocument pdfDoc1 = new PdfDocument(reader1);
PdfDocument pdfDoc2 = new PdfDocument(reader2);
PdfDocument pdfDoc3 = new PdfDocument(writer1);
PdfMerger merger = new PdfMerger(pdfDoc3).setCloseSourceDocuments(true);
merger.merge(pdfDoc, 1, 1);
merger.merge(pdfDoc1, 1, 1);
merger.merge(pdfDoc2, 1, 1);
pdfDoc3.close();
Assertions.assertNull(new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_mergedResult01.pdf", destinationFolder, "diff_"));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
})
public void mergeDocumentOutlinesWithNullDestinationTest01() throws IOException, InterruptedException {
String resultFile = destinationFolder + "mergeDocumentOutlinesWithNullDestinationTest01.pdf";
String filename = sourceFolder + "null_dest_outline.pdf";
PdfDocument sourceDocument = new PdfDocument(new PdfReader(filename));
PdfMerger resultDocument = new PdfMerger(new PdfDocument(CompareTool.createTestPdfWriter(resultFile)));
resultDocument.merge(sourceDocument, 1, 1);
resultDocument.close();
sourceDocument.close();
Assertions.assertNull(new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_mergeDocumentOutlinesWithNullDestinationTest01.pdf", destinationFolder, "diff_"));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
})
public void mergeDocumentOutlinesWithExplicitRemoteDestinationTest() throws IOException, InterruptedException {
String resultFile = destinationFolder + "mergeDocumentWithRemoteGoToTest.pdf";
String filename1 = sourceFolder + "docWithRemoteGoTo.pdf";
String filename2 = sourceFolder + "doc1.pdf";
PdfDocument sourceDocument1 = new PdfDocument(new PdfReader(filename1));
PdfDocument sourceDocument2 = new PdfDocument(new PdfReader(filename2));
PdfMerger resultDocument = new PdfMerger(new PdfDocument(CompareTool.createTestPdfWriter(resultFile)));
resultDocument.merge(sourceDocument1, 1, 1);
resultDocument.merge(sourceDocument2, 1, 1);
resultDocument.close();
sourceDocument1.close();
sourceDocument2.close();
Assertions.assertNull(new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_mergeDocumentWithRemoteGoToTest.pdf", destinationFolder, "diff_"));
}
@Test
public void mergeDocumentWithCycleRefInAcroFormTest() throws IOException, InterruptedException {
String filename1 = sourceFolder + "doc1.pdf";
String filename2 = sourceFolder + "pdfWithCycleRefInAnnotationParent.pdf";
String resultFile = destinationFolder + "resultFileWithoutStackOverflow.pdf";
try (PdfDocument pdfDocument1 = new PdfDocument(new PdfReader(filename2));
PdfDocument pdfDocument2 = new PdfDocument(new PdfReader(filename1),
CompareTool.createTestPdfWriter(resultFile).setSmartMode(true));) {
PdfMerger merger = new PdfMerger(pdfDocument2);
merger.merge(pdfDocument1, 1, pdfDocument1.getNumberOfPages());
}
Assertions.assertNull(
new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_resultFileWithoutStackOverflow.pdf",
destinationFolder, "diff_"));
}
@Test
public void mergeDocumentWithLinkAnnotationTest() throws IOException, InterruptedException {
String filename = sourceFolder + "documentWithLinkAnnotation.pdf";
String resultFile = destinationFolder + "mergedDocumentWithLinkAnnotation.pdf";
PdfReader reader = new PdfReader(filename);
PdfWriter writer1 = CompareTool.createTestPdfWriter(resultFile);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfDocument result = new PdfDocument(writer1);
PdfMerger merger = new PdfMerger(result).setCloseSourceDocuments(true);
merger.merge(pdfDoc, 1, 1).close();
Assertions.assertNull(new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_mergedDocumentWithLinkAnnotation.pdf", destinationFolder, "diff_"));
}
@Test
public void mergeDocumentTest02() throws IOException, InterruptedException {
String filename = sourceFolder + "doc1.pdf";
String filename1 = sourceFolder + "doc2.pdf";
String filename2 = sourceFolder + "doc3.pdf";
String resultFile = destinationFolder + "mergedResult02.pdf";
PdfReader reader = new PdfReader(filename);
PdfReader reader1 = new PdfReader(filename1);
PdfReader reader2 = new PdfReader(filename2);
PdfWriter writer1 = CompareTool.createTestPdfWriter(resultFile);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfDocument pdfDoc1 = new PdfDocument(reader1);
PdfDocument pdfDoc2 = new PdfDocument(reader2);
PdfDocument pdfDoc3 = new PdfDocument(writer1);
PdfMerger merger = new PdfMerger(pdfDoc3).setCloseSourceDocuments(true);
merger.merge(pdfDoc, 1, 1).merge(pdfDoc1, 1, 1).merge(pdfDoc2, 1, 1).close();
Assertions.assertNull(new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_mergedResult02.pdf", destinationFolder, "diff_"));
}
@Test
public void mergeDocumentWithCycleTagReferenceTest() throws IOException, InterruptedException {
String filename1 = sourceFolder + "doc1.pdf";
String filename2 = sourceFolder + "pdfWithCycleRefInParentTag.pdf";
String resultFile = destinationFolder + "pdfWithCycleRefInParentTag.pdf";
try (PdfDocument pdfDocument1 = new PdfDocument(new PdfReader(filename2));
PdfDocument pdfDocument2 = new PdfDocument(new PdfReader(filename1),
CompareTool.createTestPdfWriter(resultFile).setSmartMode(true));) {
PdfMerger merger = new PdfMerger(pdfDocument2);
merger.merge(pdfDocument1, 1, pdfDocument1.getNumberOfPages());
}
Assertions.assertNull(
new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_pdfWithCycleRefInParentTag.pdf",
destinationFolder, "diff_"));
}
@Test
public void mergeDocumentWithCycleReferenceInFormFieldTest() throws IOException, InterruptedException {
String filename1 = sourceFolder + "doc1.pdf";
String filename2 = sourceFolder + "pdfWithCycleRefInFormField.pdf";
String resultFile = destinationFolder + "pdfWithCycleRefInFormField.pdf";
try (PdfDocument pdfDocument1 = new PdfDocument(new PdfReader(filename2));
PdfDocument pdfDocument2 = new PdfDocument(new PdfReader(filename1),
CompareTool.createTestPdfWriter(resultFile).setSmartMode(true));) {
PdfMerger merger = new PdfMerger(pdfDocument2);
merger.merge(pdfDocument1, 1, pdfDocument1.getNumberOfPages());
}
Assertions.assertNull(
new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_pdfWithCycleRefInFormField.pdf",
destinationFolder, "diff_"));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
})
public void mergeDocumentTest03() throws IOException, InterruptedException, ParserConfigurationException, SAXException {
String filename = sourceFolder + "pdf_open_parameters.pdf";
String filename1 = sourceFolder + "iphone_user_guide.pdf";
String resultFile = destinationFolder + "mergedResult03.pdf";
PdfReader reader = new PdfReader(filename);
PdfReader reader1 = new PdfReader(filename1);
PdfWriter writer1 = CompareTool.createTestPdfWriter(resultFile);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfDocument pdfDoc1 = new PdfDocument(reader1);
PdfDocument pdfDoc3 = new PdfDocument(writer1);
pdfDoc3.setTagged();
new PdfMerger(pdfDoc3)
.merge(pdfDoc, 2, 2)
.merge(pdfDoc1, 7, 8)
.close();
pdfDoc.close();
pdfDoc1.close();
CompareTool compareTool = new CompareTool();
String errorMessage = "";
String contentErrorMessage = compareTool.compareByContent(resultFile, sourceFolder + "cmp_mergedResult03.pdf", destinationFolder, "diff_");
String tagStructErrorMessage = compareTool.compareTagStructures(resultFile, sourceFolder + "cmp_mergedResult03.pdf");
errorMessage += tagStructErrorMessage == null ? "" : tagStructErrorMessage + "\n";
errorMessage += contentErrorMessage == null ? "" : contentErrorMessage;
if (!errorMessage.isEmpty()) {
Assertions.fail(errorMessage);
}
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY),
@LogMessage(messageTemplate = IoLogMessageConstant.CREATED_ROOT_TAG_HAS_MAPPING, count = 2)
})
public void mergeDocumentTest04() throws IOException, InterruptedException, ParserConfigurationException, SAXException {
String filename = sourceFolder + "pdf_open_parameters.pdf";
String filename1 = sourceFolder + "iphone_user_guide.pdf";
String resultFile = destinationFolder + "mergedResult04.pdf";
PdfReader reader = new PdfReader(filename);
PdfReader reader1 = new PdfReader(filename1);
PdfWriter writer1 = CompareTool.createTestPdfWriter(resultFile);
PdfDocument pdfDoc = new PdfDocument(reader);
PdfDocument pdfDoc1 = new PdfDocument(reader1);
PdfDocument pdfDoc3 = new PdfDocument(writer1);
pdfDoc3.setTagged();
PdfMerger merger = new PdfMerger(pdfDoc3).setCloseSourceDocuments(true);
List<Integer> pages = new ArrayList<>();
pages.add(3);
pages.add(2);
pages.add(1);
merger.merge(pdfDoc, pages);
List<Integer> pages1 = new ArrayList<>();
pages1.add(5);
pages1.add(9);
pages1.add(4);
pages1.add(3);
merger.merge(pdfDoc1, pages1);
merger.close();
CompareTool compareTool = new CompareTool();
String errorMessage = "";
String contentErrorMessage = compareTool.compareByContent(resultFile, sourceFolder + "cmp_mergedResult04.pdf", destinationFolder, "diff_");
String tagStructErrorMessage = compareTool.compareTagStructures(resultFile, sourceFolder + "cmp_mergedResult04.pdf");
errorMessage += tagStructErrorMessage == null ? "" : tagStructErrorMessage + "\n";
errorMessage += contentErrorMessage == null ? "" : contentErrorMessage;
if (!errorMessage.isEmpty()) {
Assertions.fail(errorMessage);
}
}
@Test
public void mergeTableWithEmptyTdTest() throws IOException, ParserConfigurationException, SAXException, InterruptedException {
mergeAndCompareTagStructures("tableWithEmptyTd.pdf", 1, 1);
}
@Test
public void mergeSplitTableWithEmptyTdTest() throws IOException, ParserConfigurationException, SAXException, InterruptedException {
mergeAndCompareTagStructures("splitTableWithEmptyTd.pdf", 2, 2);
}
@Test
public void mergeEmptyRowWithTagsTest() throws IOException, ParserConfigurationException, SAXException, InterruptedException {
mergeAndCompareTagStructures("emptyRowWithTags.pdf", 1, 1);
}
@Test
@LogMessages(messages = @LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY))
public void trInsideTdTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("trInsideTdTable.pdf", 1, 1);
}
@Test
public void tdInsideTdTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("tdInsideTdTable.pdf", 1, 1);
}
@Test
public void emptyTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("emptyTrTable.pdf", 1, 1);
}
@Test
public void splitEmptyTrTableFirstPageTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("splitTableWithEmptyTrFirstPage.pdf", 1, 1);
}
@Test
public void splitEmptyTrTableSecondPageTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("splitTableWithEmptyTrSecondPage.pdf", 2, 2);
}
@Test
public void splitEmptyTrTableFullTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("splitTableWithEmptyTrFull.pdf", 1, 2);
}
@Test
public void emptyFirstTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("emptyFirstTrTable.pdf", 1, 1);
}
@Test
public void emptyLastTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("emptyLastTrTable.pdf", 1, 1);
}
@Test
public void emptyTwoAdjacentTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("emptyTwoAdjacentTrTable.pdf", 1, 1);
}
@Test
public void emptyAllTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("emptyAllTrTable.pdf", 1, 1);
}
@Test
public void emptySingleTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
mergeAndCompareTagStructures("emptySingleTrTable.pdf", 1, 1);
}
@Test
public void splitAndMergeEmptyTrTableTest() throws ParserConfigurationException, SAXException, IOException, InterruptedException {
String sourceFilename = sourceFolder + "splitTableWithEmptyTrFull.pdf";
String firstPageFilename = destinationFolder + "firstPageDoc.pdf";
String secondPageFilename = destinationFolder + "secondPageDoc.pdf";
String resultFilename = destinationFolder + "splitAndMergeEmptyTrTable.pdf";
String cmpFilename = sourceFolder + "cmp_splitAndMergeEmptyTrTable.pdf";
PdfDocument sourceDoc = new PdfDocument(new PdfReader(sourceFilename));
PdfDocument firstPageDoc = new PdfDocument(new PdfWriter(firstPageFilename));
PdfMerger mergerFirstPage = new PdfMerger(firstPageDoc);
mergerFirstPage.merge(sourceDoc, 1, 1);
mergerFirstPage.close();
PdfDocument secondPageDoc = new PdfDocument(new PdfWriter(secondPageFilename));
PdfMerger mergerSecondPage = new PdfMerger(secondPageDoc);
mergerSecondPage.merge(sourceDoc, 2, 2);
mergerSecondPage.close();
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPageFilename));
sources.add(new File(secondPageFilename));
mergePdfs(sources, resultFilename, new PdfMergerProperties(), false);
Assertions.assertNull(new CompareTool().compareTagStructures(resultFilename, cmpFilename));
}
@Test
@LogMessages(messages = {@LogMessage(messageTemplate = IoLogMessageConstant.NAME_ALREADY_EXISTS_IN_THE_NAME_TREE, count = 2)})
public void mergeOutlinesNamedDestinations() throws IOException, InterruptedException {
String filename = sourceFolder + "outlinesNamedDestinations.pdf";
String resultFile = destinationFolder + "mergeOutlinesNamedDestinations.pdf";
PdfReader reader = new PdfReader(filename);
PdfDocument sourceDoc = new PdfDocument(reader);
PdfDocument output = new PdfDocument(CompareTool.createTestPdfWriter(resultFile));
PdfMerger merger = new PdfMerger(output).setCloseSourceDocuments(false);
merger.merge(sourceDoc, 2, 3);
merger.merge(sourceDoc, 2, 3);
sourceDoc.close();
reader.close();
merger.close();
output.close();
CompareTool compareTool = new CompareTool();
String errorMessage = compareTool.compareByContent(resultFile, sourceFolder + "cmp_mergeOutlinesNamedDestinations.pdf", destinationFolder, "diff_");
if (errorMessage != null) {
Assertions.fail(errorMessage);
}
}
@Test
// TODO DEVSIX-1743. Update cmp file after fix
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
})
public void mergeWithAcroFormsTest() throws IOException, InterruptedException {
String pdfAcro1 = sourceFolder + "pdfSource1.pdf";
String pdfAcro2 = sourceFolder + "pdfSource2.pdf";
String outFileName = destinationFolder + "mergeWithAcroFormsTest.pdf";
String cmpFileName= sourceFolder + "cmp_mergeWithAcroFormsTest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(pdfAcro1));
sources.add(new File(pdfAcro2));
mergePdfs(sources, outFileName, false);
Assertions.assertNull(new CompareTool().compareByContent(outFileName, cmpFileName, destinationFolder));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_HAS_CONFLICTING_OCG_NAMES, count = 3)
})
public void mergePdfWithOCGTest() throws IOException, InterruptedException {
String pdfWithOCG1 = sourceFolder + "sourceOCG1.pdf";
String pdfWithOCG2 = sourceFolder + "sourceOCG2.pdf";
String outPdf = destinationFolder + "mergePdfWithOCGTest.pdf";
String cmpPdf = sourceFolder + "cmp_mergePdfWithOCGTest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(pdfWithOCG1));
sources.add(new File(pdfWithOCG2));
sources.add(new File(pdfWithOCG2));
sources.add(new File(pdfWithOCG2));
mergePdfs(sources, outPdf, false);
Assertions.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_HAS_CONFLICTING_OCG_NAMES)
})
public void mergePdfWithComplexOCGTest() throws IOException, InterruptedException {
String pdfWithOCG1 = sourceFolder + "sourceOCG1.pdf";
String pdfWithOCG2 = sourceFolder + "pdfWithComplexOCG.pdf";
String outPdf = destinationFolder + "mergePdfWithComplexOCGTest.pdf";
String cmpPdf = sourceFolder + "cmp_mergePdfWithComplexOCGTest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(pdfWithOCG1));
sources.add(new File(pdfWithOCG2));
mergePdfs(sources, outPdf, false);
Assertions.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_HAS_CONFLICTING_OCG_NAMES)
})
public void mergeTwoPagePdfWithComplexOCGTest() throws IOException, InterruptedException {
String pdfWithOCG1 = sourceFolder + "sourceOCG1.pdf";
String pdfWithOCG2 = sourceFolder + "twoPagePdfWithComplexOCGTest.pdf";
String outPdf = destinationFolder + "mergeTwoPagePdfWithComplexOCGTest.pdf";
String cmpPdf = sourceFolder + "cmp_mergeTwoPagePdfWithComplexOCGTest.pdf";
PdfDocument mergedDoc = new PdfDocument(CompareTool.createTestPdfWriter(outPdf));
PdfMerger merger = new PdfMerger(mergedDoc);
List<File> sources = new ArrayList<File>();
sources.add(new File(pdfWithOCG1));
sources.add(new File(pdfWithOCG2));
// The test verifies that are copying only those OCGs and properties that are used on the copied pages
for(File source : sources){
PdfDocument sourcePdf = new PdfDocument(new PdfReader(source));
merger.merge(sourcePdf, 1, 1).setCloseSourceDocuments(true);
sourcePdf.close();
}
merger.close();
mergedDoc.close();
Assertions.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder));
}
@Test
public void mergePdfWithComplexOCGTwiceTest() throws IOException, InterruptedException {
String pdfWithOCG = sourceFolder + "pdfWithComplexOCG.pdf";
String outPdf = destinationFolder + "mergePdfWithComplexOCGTwiceTest.pdf";
String cmpPdf = sourceFolder + "cmp_mergePdfWithComplexOCGTwiceTest.pdf";
PdfDocument mergedDoc = new PdfDocument(CompareTool.createTestPdfWriter(outPdf));
PdfMerger merger = new PdfMerger(mergedDoc);
PdfDocument sourcePdf = new PdfDocument(new PdfReader(new File(pdfWithOCG)));
// The test verifies that identical layers from the same document are not copied
merger.merge(sourcePdf, 1, sourcePdf.getNumberOfPages());
merger.merge(sourcePdf, 1, sourcePdf.getNumberOfPages());
sourcePdf.close();
merger.close();
mergedDoc.close();
Assertions.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder));
}
@Test
public void stackOverflowErrorCycleReferenceOcgMergeTest() throws IOException, InterruptedException {
String outPdf = destinationFolder + "cycleReferenceMerged.pdf";
String cmpPdf = sourceFolder + "cmp_stackOverflowErrorCycleReferenceOcrMerge.pdf";
PdfDocument pdfWithOCG = new PdfDocument(new PdfReader(sourceFolder + "sourceOCG1.pdf"),
CompareTool.createTestPdfWriter(outPdf));
PdfDocument pdfWithOCGToMerge = new PdfDocument
(new PdfReader( sourceFolder + "stackOverflowErrorCycleReferenceOcgMerge.pdf")); // problem file
PdfMerger merger = new PdfMerger(pdfWithOCG);
merger.merge(pdfWithOCGToMerge, 1, pdfWithOCGToMerge.getNumberOfPages());
pdfWithOCGToMerge.close();
pdfWithOCG.close();
Assertions.assertNull(new CompareTool().compareByContent(outPdf, cmpPdf, destinationFolder));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
})
public void mergeOutlinesWithWrongStructureTest() throws IOException, InterruptedException {
PdfDocument inputDoc = new PdfDocument(new PdfReader(
sourceFolder + "infiniteLoopInOutlineStructure.pdf"));
PdfDocument outputDoc = new PdfDocument(CompareTool.createTestPdfWriter(
destinationFolder + "infiniteLoopInOutlineStructure.pdf"));
PdfMerger merger = new PdfMerger(outputDoc, new PdfMergerProperties().setMergeTags(false).setMergeOutlines(true));
System.out.println("Doing merge");
merger.merge(inputDoc, 1, 2);
merger.close();
System.out.println("Merge done");
Assertions.assertNull(new CompareTool().compareByContent(
destinationFolder + "infiniteLoopInOutlineStructure.pdf",
sourceFolder + "cmp_infiniteLoopInOutlineStructure.pdf", destinationFolder));
}
private static void mergeAndCompareTagStructures(String testName, int fromPage, int toPage)
throws IOException, ParserConfigurationException, SAXException, InterruptedException {
String src = sourceFolder + testName;
String dest = destinationFolder + testName;
String cmp = sourceFolder + "cmp_" + testName;
PdfReader reader = new PdfReader(src);
PdfDocument sourceDoc = new PdfDocument(reader);
PdfDocument output = new PdfDocument(CompareTool.createTestPdfWriter(dest));
output.setTagged();
PdfMerger merger = new PdfMerger(output).setCloseSourceDocuments(true);
merger.merge(sourceDoc, fromPage, toPage);
sourceDoc.close();
reader.close();
merger.close();
output.close();
Assertions.assertNull(new CompareTool().compareTagStructures(dest, cmp));
}
@Test
public void mergeDocumentWithColorPropertyInOutlineTest() throws IOException, InterruptedException {
String firstDocument = sourceFolder + "firstDocumentWithColorPropertyInOutline.pdf";
String secondDocument = sourceFolder + "SecondDocumentWithColorPropertyInOutline.pdf";
String cmpDocument = sourceFolder + "cmp_mergeOutlinesWithColorProperty.pdf";
String mergedPdf = destinationFolder + "mergeOutlinesWithColorProperty.pdf";
try (PdfDocument merged = new PdfDocument(CompareTool.createTestPdfWriter(mergedPdf));
PdfDocument fileA = new PdfDocument(new PdfReader(firstDocument));
PdfDocument fileB = new PdfDocument(new PdfReader(secondDocument))) {
PdfMerger merger = new PdfMerger(merged, new PdfMergerProperties().setMergeTags(false).setMergeOutlines(true));
merger.merge(fileA, 1, fileA.getNumberOfPages());
merger.merge(fileB, 1, fileB.getNumberOfPages());
merger.close();
}
Assertions.assertNull(new CompareTool().compareByContent(mergedPdf, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentWithStylePropertyInOutlineTest() throws IOException, InterruptedException {
String firstDocument = sourceFolder + "firstDocumentWithStylePropertyInOutline.pdf";
String secondDocument = sourceFolder + "secondDocumentWithStylePropertyInOutline.pdf";
String cmpPdf = sourceFolder + "cmp_mergeOutlineWithStyleProperty.pdf";
String mergedPdf = destinationFolder + "mergeOutlineWithStyleProperty.pdf";
try (PdfDocument documentA = new PdfDocument(new PdfReader(firstDocument));
PdfDocument documentB = new PdfDocument(new PdfReader(secondDocument));
PdfDocument merged = new PdfDocument(CompareTool.createTestPdfWriter(mergedPdf))) {
PdfMerger merger = new PdfMerger(merged, new PdfMergerProperties().setMergeTags(false).setMergeOutlines(true));
merger.merge(documentA, 1, documentA.getNumberOfPages());
merger.merge(documentB, 1, documentB.getNumberOfPages());
merger.close();
}
Assertions.assertNull(new CompareTool().compareByContent(mergedPdf, cmpPdf, destinationFolder));
}
@Test
public void mergePdfDocumentsWithCopingOutlinesTest() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "firstDocumentWithOutlines.pdf";
String secondPdfDocument = sourceFolder + "secondDocumentWithOutlines.pdf";
String cmpDocument = sourceFolder + "cmp_mergeDocumentsWithOutlines.pdf";
String mergedDocument = destinationFolder + "mergeDocumentsWithOutlines.pdf";
try (PdfDocument documentA = new PdfDocument(new PdfReader(firstPdfDocument));
PdfDocument documentB = new PdfDocument(new PdfReader(secondPdfDocument));
PdfDocument mergedPdf = new PdfDocument(CompareTool.createTestPdfWriter(mergedDocument))) {
PdfMerger merger = new PdfMerger(mergedPdf, new PdfMergerProperties().setMergeTags(false).setMergeOutlines(true));
merger.merge(documentA, 1, documentA.getNumberOfPages());
merger.merge(documentB, 1, documentB.getNumberOfPages());
merger.close();
}
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void MergeWithSameNamedOcgTest() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "sameNamdOCGSource.pdf";
String secondPdfDocument = sourceFolder + "doc2.pdf";
String cmpDocument = sourceFolder + "cmp_MergeWithSameNamedOCG.pdf";
String mergedDocument = destinationFolder + "mergeWithSameNamedOCG.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
// We have to compare visually also because compareByContent doesn't catch the differences in OCGs with the same names
Assertions.assertNull(new CompareTool().compareVisually(mergedDocument, cmpDocument, destinationFolder, "diff_"));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY),
@LogMessage(messageTemplate = IoLogMessageConstant.DOCUMENT_HAS_CONFLICTING_OCG_NAMES)
})
public void MergeWithSameNamedOcgOcmdDTest() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "Layer doc1.pdf";
String secondPdfDocument = sourceFolder + "Layer doc2.pdf";
String cmpDocument = sourceFolder + "cmp_mergeWithSameNamedOCMD.pdf";
String mergedDocument = destinationFolder + "mergeWithSameNamedOCMD.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED)
})
public void mergePdfWithMissingStructElemBeginningOfTreeTest() throws IOException, InterruptedException {
String name = "structParentMissingFirstElement.pdf";
Assertions.assertNotNull(mergeSinglePdfAndGetResultingStructTreeRoot(name));
Assertions.assertNull(new CompareTool().compareByContent(
destinationFolder + name,
sourceFolder + "cmp_" + name, destinationFolder));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY),
@LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED)
})
public void mergePdfWithMissingStructElemEndOfTreeTest() throws IOException, InterruptedException {
String name = "structParentMissingLastElement.pdf";
Assertions.assertNotNull(mergeSinglePdfAndGetResultingStructTreeRoot(name));
Assertions.assertNull(new CompareTool().compareByContent(
destinationFolder + name,
sourceFolder + "cmp_" + name, destinationFolder));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY),
@LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED, count = 4)
})
public void mergePdfAllObjectsMissingStructParentTest() throws IOException, InterruptedException {
String name = "allObjectsHaveStructParent.pdf";
Assertions.assertNotNull(mergeSinglePdfAndGetResultingStructTreeRoot(name));
Assertions.assertNull(new CompareTool().compareByContent(
destinationFolder + name,
sourceFolder + "cmp_" + name, destinationFolder));
}
@Test
@LogMessages(messages = {
@LogMessage(messageTemplate = KernelLogMessageConstant.STRUCT_PARENT_INDEX_MISSED_AND_RECREATED, count = 2)
})
public void mergePdfChildObjectsOfSameStructElemMissingStructParentTest() throws IOException, InterruptedException {
String name = "SameStructElemNoParent.pdf";
Assertions.assertNotNull(mergeSinglePdfAndGetResultingStructTreeRoot(name));
Assertions.assertNull(new CompareTool().compareByContent(
destinationFolder + name,
sourceFolder + "cmp_" + name, destinationFolder));
}
@Test
public void mergeDocumentsWithStringAdditionalActions() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "docAAString.pdf";
String cmpDocument = sourceFolder + "cmp_mergeAAString.pdf";
String mergedDocument = destinationFolder + "mergedAAString.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
@LogMessages(messages = {@LogMessage(messageTemplate = KernelLogMessageConstant.CANNOT_MERGE_ENTRY)})
public void mergeDocumentsWithAdditionalActionsInDestination() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "docAAStream.pdf";
String secondPdfDocument = sourceFolder + "docAAString.pdf";
String cmpDocument = sourceFolder + "cmp_mergeAA2.pdf";
String mergedDocument = destinationFolder + "mergedAAInDest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentsWithUnexpectedKeyAdditionalActions() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "docAAStringWithUnexpectedKey.pdf";
String cmpDocument = sourceFolder + "cmp_mergeAAUnexpectedKey.pdf";
String mergedDocument = destinationFolder + "mergedAAUnexpectedKey.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentsWithStreamAdditionalActions() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "docAAStream.pdf";
String cmpDocument = sourceFolder + "cmp_mergeAAStream.pdf";
String mergedDocument = destinationFolder + "mergedAAStream.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentsWithStringOpenActions() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "docOAString.pdf";
String cmpDocument = sourceFolder + "cmp_mergeOAString.pdf";
String mergedDocument = destinationFolder + "mergedOAString.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
@LogMessages(messages = {@LogMessage(messageTemplate = KernelLogMessageConstant.CANNOT_MERGE_ENTRY)})
public void mergeDocumentsWithOpenActionInDestination() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "docOAStream.pdf";
String secondPdfDocument = sourceFolder + "docOAString.pdf";
String cmpDocument = sourceFolder + "cmp_mergeOA2.pdf";
String mergedDocument = destinationFolder + "mergedOAInDest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentsWithStreamOpenActions() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "docOAStream.pdf";
String cmpDocument = sourceFolder + "cmp_mergeOAStream.pdf";
String mergedDocument = destinationFolder + "mergedOAStream.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentsWithJSInTree() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "docJS.pdf";
String cmpDocument = sourceFolder + "cmp_mergeJS.pdf";
String mergedDocument = destinationFolder + "mergedJS.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentsWithNullDestination() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "linkAnnotationWithNullDestinationTest.pdf";
String cmpDocument = sourceFolder + "cmp_linkAnnotationWithNullDestinationTest.pdf";
String mergedDocument = destinationFolder + "mergedLinkAnnotationWithNullDestinationTest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentsWithNullDestinationInGoTo() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "linkAnnotationWithNullDestinationInGoToTest.pdf";
String cmpDocument = sourceFolder + "cmp_linkAnnotationWithNullDestinationInGoToTest.pdf";
String mergedDocument = destinationFolder + "mergedLinkAnnotationWithNullDestinationInGoToTest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void mergeDocumentsWithPdfNullDestinationInGoTo() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "doc1.pdf";
String secondPdfDocument = sourceFolder + "linkAnnotationWithPdfNullDestinationInGoToTest.pdf";
String cmpDocument = sourceFolder + "cmp_linkAnnotationWithPdfNullDestinationInGoToTest.pdf";
String mergedDocument = destinationFolder + "mergedLinkAnnotationWithPdfNullDestinationInGoToTest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
@LogMessages(messages = {@LogMessage(messageTemplate = KernelLogMessageConstant.CANNOT_MERGE_ENTRY)})
public void mergeDocumentsWithNamesJSInDestination() throws IOException, InterruptedException {
String firstPdfDocument = sourceFolder + "cmp_mergeJS.pdf";
String secondPdfDocument = sourceFolder + "docJS.pdf";
String cmpDocument = sourceFolder + "cmp_mergeJS2.pdf";
String mergedDocument = destinationFolder + "mergedJSInDest.pdf";
List<File> sources = new ArrayList<File>();
sources.add(new File(firstPdfDocument));
sources.add(new File(secondPdfDocument));
mergePdfs(sources, mergedDocument, new PdfMergerProperties().setMergeScripts(true), true);
Assertions.assertNull(new CompareTool().compareByContent(mergedDocument, cmpDocument, destinationFolder));
}
@Test
public void copyEmptyOcPropertiesTest() throws IOException, InterruptedException {
String filename = sourceFolder + "emptyOcPropertiesDoc.pdf";
String resultFile = destinationFolder + "mergedEmptyOcPropertiesDoc.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfReader(filename));
PdfDocument result = new PdfDocument(CompareTool.createTestPdfWriter(resultFile));
PdfMerger merger = new PdfMerger(result).setCloseSourceDocuments(true);
merger.merge(pdfDoc, 1, 1).close();
Assertions.assertNull(new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_mergedEmptyOcPropertiesDoc.pdf", destinationFolder, "diff_"));
}
@Test
public void copyOnlyEmptyOcPropertiesTest() throws IOException, InterruptedException {
String filename = sourceFolder + "ocPropertiesDoc.pdf";
String resultFile = destinationFolder + "mergedOcPropertiesDoc.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfReader(filename));
PdfDocument result = new PdfDocument(CompareTool.createTestPdfWriter(resultFile));
PdfMerger merger = new PdfMerger(result).setCloseSourceDocuments(true);
merger.merge(pdfDoc, 1, 1).close();
Assertions.assertNull(new CompareTool().compareByContent(resultFile, sourceFolder + "cmp_mergedOcPropertiesDoc.pdf", destinationFolder, "diff_"));
}
@LogMessages(messages = {
@LogMessage(messageTemplate = IoLogMessageConstant.SOURCE_DOCUMENT_HAS_ACROFORM_DICTIONARY)
})
@Test
public void combineTagRootKidsTest() throws IOException, InterruptedException {
String filename1 = sourceFolder + "tagRootKidsDoc1.pdf";
String filename2 = sourceFolder + "tagRootKidsDoc2.pdf";
String resultFile = destinationFolder + "mergedTags.pdf";
PdfDocument result = new PdfDocument(CompareTool.createTestPdfWriter(resultFile));
PdfMerger merger = new PdfMerger(result, new PdfMergerProperties().setMergeTags(true).setMergeOutlines(true))
.setCloseSourceDocuments(true);
PdfDocument input1 = new PdfDocument(new PdfReader(filename1));
merger.merge(input1, 1, 1);
input1.close();
PdfDocument input2 = new PdfDocument(new PdfReader(filename2));
merger.merge(input2, 1, 1);
input2.close();
merger.close();
Assertions.assertNull(new CompareTool()
.compareByContent(resultFile, sourceFolder + "cmp_mergedTags.pdf", destinationFolder, "diff_"));
}
private PdfDictionary mergeSinglePdfAndGetResultingStructTreeRoot(String pathToMerge)
throws IOException {
List<File> sources = new ArrayList<File>();
sources.add(new File(sourceFolder + pathToMerge));
String mergedDoc = destinationFolder + pathToMerge;
mergePdfs(sources, mergedDoc, true);
return getStructTreeRootOfDocument(mergedDoc);
}
private PdfDictionary getStructTreeRootOfDocument(String pathToFile) throws IOException {
PdfDocument mergedDocument = new PdfDocument(new PdfReader(pathToFile));
return mergedDocument.getCatalog().getPdfObject()
.getAsDictionary(PdfName.StructTreeRoot);
}
private void mergePdfs(List<File> sources, String destination, boolean smartMode) throws IOException {
PdfDocument mergedDoc = new PdfDocument(new PdfWriter(destination));
mergedDoc.getWriter().setSmartMode(smartMode);
PdfMerger merger = new PdfMerger(mergedDoc);
for (File source: sources) {
PdfDocument sourcePdf = new PdfDocument(new PdfReader(source));
merger.merge(sourcePdf, 1, sourcePdf.getNumberOfPages()).setCloseSourceDocuments(true);
sourcePdf.close();
}
merger.close();
mergedDoc.close();
}
private void mergePdfs(List<File> sources, String destination, PdfMergerProperties properties, boolean smartMode) throws IOException {
PdfDocument mergedDoc = new PdfDocument(CompareTool.createTestPdfWriter(destination));
mergedDoc.getWriter().setSmartMode(smartMode);
PdfMerger merger = new PdfMerger(mergedDoc, properties);
for (File source: sources) {
PdfDocument sourcePdf = new PdfDocument(new PdfReader(source));
merger.merge(sourcePdf, 1, sourcePdf.getNumberOfPages()).setCloseSourceDocuments(true);
sourcePdf.close();
}
merger.close();
mergedDoc.close();
}
}