Combine two (or more) PDF's

134,694

Solution 1

I had to solve a similar problem and what I ended up doing was creating a small pdfmerge utility that uses the PDFSharp project which is essentially MIT licensed.

The code is dead simple, I needed a cmdline utility so I have more code dedicated to parsing the arguments than I do for the PDF merging:

using (PdfDocument one = PdfReader.Open("file1.pdf", PdfDocumentOpenMode.Import))
using (PdfDocument two = PdfReader.Open("file2.pdf", PdfDocumentOpenMode.Import))
using (PdfDocument outPdf = new PdfDocument())
{                
    CopyPages(one, outPdf);
    CopyPages(two, outPdf);

    outPdf.Save("file1and2.pdf");
}

void CopyPages(PdfDocument from, PdfDocument to)
{
    for (int i = 0; i < from.PageCount; i++)
    {
        to.AddPage(from.Pages[i]);
    }
}

Solution 2

Here is a single function that will merge X amount of PDFs using PDFSharp

using PdfSharp;
using PdfSharp.Pdf;
using PdfSharp.Pdf.IO;

public static void MergePDFs(string targetPath, params string[] pdfs) {
    using(var targetDoc = new PdfDocument()){
        foreach (var pdf in pdfs) {
            using (var pdfDoc = PdfReader.Open(pdf, PdfDocumentOpenMode.Import)) {
                for (var i = 0; i < pdfDoc.PageCount; i++) 
                    targetDoc.AddPage(pdfDoc.Pages[i]);
            }
        }
        targetDoc.Save(targetPath);
    }
}

Solution 3

This is something that I figured out, and wanted to share with you, using PdfSharp.

Here you can join multiple Pdfs in one, without the need of an output directory (following the input list order)

    public static byte[] MergePdf(List<byte[]> pdfs)
    {
        List<PdfSharp.Pdf.PdfDocument> lstDocuments = new List<PdfSharp.Pdf.PdfDocument>();
        foreach (var pdf in pdfs)
        {
            lstDocuments.Add(PdfReader.Open(new MemoryStream(pdf), PdfDocumentOpenMode.Import));
        }

        using (PdfSharp.Pdf.PdfDocument outPdf = new PdfSharp.Pdf.PdfDocument())
        { 
            for(int i = 1; i<= lstDocuments.Count; i++)
            {
                foreach(PdfSharp.Pdf.PdfPage page in lstDocuments[i-1].Pages)
                {
                    outPdf.AddPage(page);
                }
            }

            MemoryStream stream = new MemoryStream();
            outPdf.Save(stream, false);
            byte[] bytes = stream.ToArray();

            return bytes;
        }           
    }

Solution 4

Here is a example using iTextSharp

public static void MergePdf(Stream outputPdfStream, IEnumerable<string> pdfFilePaths)
{
    using (var document = new Document())
    using (var pdfCopy = new PdfCopy(document, outputPdfStream))
    {
        pdfCopy.CloseStream = false;
        try
        {
            document.Open();
            foreach (var pdfFilePath in pdfFilePaths)
            {
                using (var pdfReader = new PdfReader(pdfFilePath))
                {
                    pdfCopy.AddDocument(pdfReader);
                    pdfReader.Close();
                }
            }
        }
        finally
        {
            document?.Close();
        }
    }
}

The PdfReader constructor has many overloads. It's possible to replace the parameter type IEnumerable<string> with IEnumerable<Stream> and it should work as well. Please notice that the method does not close the OutputStream, it delegates that task to the Stream creator.

Solution 5

I used iTextsharp with c# to combine pdf files. This is the code I used.

string[] lstFiles=new string[3];
    lstFiles[0]=@"C:/pdf/1.pdf";
    lstFiles[1]=@"C:/pdf/2.pdf";
    lstFiles[2]=@"C:/pdf/3.pdf";

    PdfReader reader = null;
    Document sourceDocument = null;
    PdfCopy pdfCopyProvider = null;
    PdfImportedPage importedPage;
    string outputPdfPath=@"C:/pdf/new.pdf";


    sourceDocument = new Document();
    pdfCopyProvider = new PdfCopy(sourceDocument, new System.IO.FileStream(outputPdfPath, System.IO.FileMode.Create));

    //Open the output file
    sourceDocument.Open();

    try
    {
        //Loop through the files list
        for (int f = 0; f < lstFiles.Length-1; f++)
        {
            int pages =get_pageCcount(lstFiles[f]);

            reader = new PdfReader(lstFiles[f]);
            //Add pages of current file
            for (int i = 1; i <= pages; i++)
            {
                importedPage = pdfCopyProvider.GetImportedPage(reader, i);
                pdfCopyProvider.AddPage(importedPage);
            }

            reader.Close();
         }
        //At the end save the output file
        sourceDocument.Close();
    }
    catch (Exception ex)
    {
        throw ex;
    }


private int get_pageCcount(string file)
{
    using (StreamReader sr = new StreamReader(File.OpenRead(file)))
    {
        Regex regex = new Regex(@"/Type\s*/Page[^s]");
        MatchCollection matches = regex.Matches(sr.ReadToEnd());

        return matches.Count;
    }
}
Share:
134,694
Nathan Koop
Author by

Nathan Koop

I'm currently a Sr. Dev/tech lead at Bold Commerce. I work with PHP, Laravel, Eloquent, React, Redux, GoLang, MySQL and more.

Updated on July 08, 2022

Comments

  • Nathan Koop
    Nathan Koop almost 2 years

    Background: I need to provide a weekly report package for my sales staff. This package contains several (5-10) crystal reports.

    Problem: I would like to allow a user to run all reports and also just run a single report. I was thinking I could do this by creating the reports and then doing:

    List<ReportClass> reports = new List<ReportClass>();
    reports.Add(new WeeklyReport1());
    reports.Add(new WeeklyReport2());
    reports.Add(new WeeklyReport3());
    <snip>
    
    foreach (ReportClass report in reports)
    {
        report.ExportToDisk(ExportFormatType.PortableDocFormat, @"c:\reports\" + report.ResourceName + ".pdf");
    }
    

    This would provide me a folder full of the reports, but I would like to email everyone a single PDF with all the weekly reports. So I need to combine them.

    Is there an easy way to do this without install any more third party controls? I already have DevExpress & CrystalReports and I'd prefer not to add too many more.

    Would it be best to combine them in the foreach loop or in a seperate loop? (or an alternate way)