0
\$\begingroup\$

As a part of a project I'm working on, a new requirement has arisen for me to be able to generate multiple PDF files and then merge them together before they are delivered to our client(s). For this, I've started working on a reusable solution that implements the iTextSharp library using code I've taken from the Java example listed on page 187 of the sample chapter from "iText in Action". I've attempted to make it able to handle a number of possible scenarios through overloaded methods, and everything seems to be working correctly, as far as I can tell.

What I'm most concerned about at this point is doing my best to put in appropriate error handling. Since this is my first time working with this library, I'm not sure yet about any of the "gotcha" exceptions that I might be overlooking.

''' <summary>
''' Class for manipulating PDF files
''' </summary>
Public Class PDFManipulator
#Region "PUBLIC PROPERTIES"
    Public Property Warnings As List(Of String)
    Public Property Errors As List(Of String)
#End Region
    ''' <summary>
    ''' The file property to use for sorting the files before merging
    ''' </summary>
    Public Enum PDFMergeSortOrder
        Original
        FileDate
        FileName
        FileNameWithDirectory
    End Enum

#Region "PUBLIC METHODS"
#Region "OVERLOAD METHODS"
    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, OutputFileName, False, PDFMergeSortOrder.Original, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, OutputFileName, False, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, OutputFileName, False, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, OutputFileName, False, SortOrder, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, OutputFileName, OverwriteExistingPDF, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Dim PDFFiles As New List(Of System.IO.FileInfo)
        Dim PDFFolder As New System.IO.DirectoryInfo(PDFFilePath)

        For Each PDF As System.IO.FileInfo In PDFFolder.GetFiles("*.pdf")
            If PDF.Length > 0 Then
                PDFFiles.Add(PDF)
            Else
                If Warnings Is Nothing Then
                    Warnings = New List(Of String)
                End If

                Warnings.Add("Empty file : " & PDF.FullName & " not merged into output file.")
            End If
        Next PDF

        If RecurseSubFolders Then
            PDFFiles.AddRange(GetFilesFromSubFoldersForMerge(PDFFolder))
        End If

        Return Merge(PDFFiles, OutputFileName, OverwriteExistingPDF, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, OutputFileName, False, PDFMergeSortOrder.Original, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, OutputFileName, False, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, OutputFileName, False, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, OutputFileName, False, SortOrder, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, OutputFileName, OverwriteExistingPDF, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Dim PDFFiles As New List(Of System.IO.FileInfo)

        For Each PDF As System.IO.FileInfo In PDFFileDirectory.GetFiles("*.pdf").OrderBy(Function(f As System.IO.FileInfo) f.LastWriteTime)
            If PDF.Length > 0 Then
                PDFFiles.Add(PDF)
            Else
                If Warnings Is Nothing Then
                    Warnings = New List(Of String)
                End If

                Warnings.Add("Empty file : " & PDF.FullName & " not merged into output file.")
            End If
        Next PDF

        If RecurseSubFolders Then
            PDFFiles.AddRange(GetFilesFromSubFoldersForMerge(PDFFileDirectory))
        End If

        Return Merge(PDFFiles, OutputFileName, OverwriteExistingPDF, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileNames">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFileNames As List(Of String), ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return Merge(PDFFileNames, OutputFileName, False, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileNames">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFileNames As List(Of String), ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean) As System.IO.FileInfo
        Return Merge(PDFFileNames, OutputFileName, False, PDFMergeSortOrder.Original)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileNames">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFileNames As List(Of String), ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Dim PDFFiles As New List(Of System.IO.FileInfo)

        For Each PDFName As String In PDFFileNames
            If System.IO.File.Exists(PDFName) Then
                Dim PDF As New System.IO.FileInfo(PDFName)

                If PDF.Length > 0 Then
                    PDFFiles.Add(PDF)
                Else
                    If Warnings Is Nothing Then
                        Warnings = New List(Of String)
                    End If

                    Warnings.Add("Empty file : " & PDF.FullName & " not merged into output file.")
                End If
            End If
        Next PDFName

        Return Merge(PDFFiles, OutputFileName, OverwriteExistingPDF, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFiles">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFiles As List(Of System.IO.FileInfo), ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean) As System.IO.FileInfo
        Return Merge(PDFFiles, OutputFileName, False, PDFMergeSortOrder.Original)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFiles">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFiles As List(Of System.IO.FileInfo), ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return Merge(PDFFiles, OutputFileName, False, SortOrder)
    End Function
#End Region

#Region "MASTER MERGE METHOD"
    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFiles">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFiles As List(Of System.IO.FileInfo), ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Dim ResultFile As System.IO.FileInfo = Nothing
        Dim ContinueMerge As Boolean = True

        If OverwriteExistingPDF Then
            If System.IO.File.Exists(OutputFileName) Then
                Try
                    System.IO.File.Delete(OutputFileName)
                Catch ex As Exception
                    ContinueMerge = False

                    If Errors Is Nothing Then
                        Errors = New List(Of String)
                    End If

                    Errors.Add("Could not delete existing output file.")

                    Throw
                End Try
            End If
        End If

        If ContinueMerge Then
            Dim OutputPDF As iTextSharp.text.Document = Nothing
            Dim Copier As iTextSharp.text.pdf.PdfCopy = Nothing
            Dim PDFStream As System.IO.FileStream = Nothing
            Dim SortedList As New List(Of System.IO.FileInfo)

            Try
                Select Case SortOrder
                    Case PDFMergeSortOrder.Original
                        SortedList = PDFFiles
                    Case PDFMergeSortOrder.FileDate
                        SortedList = PDFFiles.OrderBy(Function(f As System.IO.FileInfo) f.LastWriteTime).ToList
                    Case PDFMergeSortOrder.FileName
                        SortedList = PDFFiles.OrderBy(Function(f As System.IO.FileInfo) f.Name).ToList
                    Case PDFMergeSortOrder.FileNameWithDirectory
                        SortedList = PDFFiles.OrderBy(Function(f As System.IO.FileInfo) f.FullName).ToList
                End Select

                If Not IO.Directory.Exists(New IO.FileInfo(OutputFileName).DirectoryName) Then
                    Try
                        IO.Directory.CreateDirectory(New IO.FileInfo(OutputFileName).DirectoryName)
                    Catch ex As Exception
                        ContinueMerge = False

                        If Errors Is Nothing Then
                            Errors = New List(Of String)
                        End If

                        Errors.Add("Could not create output directory.")

                        Throw
                    End Try
                End If

                If ContinueMerge Then
                    OutputPDF = New iTextSharp.text.Document
                    PDFStream = New System.IO.FileStream(OutputFileName, System.IO.FileMode.OpenOrCreate)
                    Copier = New iTextSharp.text.pdf.PdfCopy(OutputPDF, PDFStream)

                    OutputPDF.Open()

                    For Each PDF As System.IO.FileInfo In SortedList
                        If ContinueMerge Then
                            Dim InputReader As iTextSharp.text.pdf.PdfReader = Nothing

                            Try
                                InputReader = New iTextSharp.text.pdf.PdfReader(PDF.FullName)

                                For page As Integer = 1 To InputReader.NumberOfPages
                                    Copier.AddPage(Copier.GetImportedPage(InputReader, page))
                                Next page

                                If InputReader.IsRebuilt Then
                                    If Warnings Is Nothing Then
                                        Warnings = New List(Of String)
                                    End If

                                    Warnings.Add("Damaged PDF: " & PDF.FullName & " repaired and successfully merged into output file.")
                                End If
                            Catch InvalidEx As iTextSharp.text.exceptions.InvalidPdfException
                                'Skip this file
                                If Errors Is Nothing Then
                                    Errors = New List(Of String)
                                End If

                                Errors.Add("Invalid PDF: " & PDF.FullName & " not merged into output file.")
                            Catch FormatEx As iTextSharp.text.pdf.BadPdfFormatException
                                'Skip this file
                                If Errors Is Nothing Then
                                    Errors = New List(Of String)
                                End If

                                Errors.Add("Bad PDF Format: " & PDF.FullName & " not merged into output file.")
                            Catch PassworddEx As iTextSharp.text.exceptions.BadPasswordException
                                'Skip this file
                                If Errors Is Nothing Then
                                    Errors = New List(Of String)
                                End If

                                Errors.Add("Password-protected PDF: " & PDF.FullName & " not merged into output file.")
                            Catch OtherEx As Exception
                                ContinueMerge = False
                            Finally
                                If Not InputReader Is Nothing Then
                                    InputReader.Close()
                                    InputReader.Dispose()
                                End If
                            End Try
                        End If
                    Next PDF
                End If
            Catch ex As iTextSharp.text.pdf.PdfException
                ResultFile = Nothing
                ContinueMerge = False

                If Errors Is Nothing Then
                    Errors = New List(Of String)
                End If

                Errors.Add("iTextSharp Error: " & ex.Message)

                If System.IO.File.Exists(OutputFileName) Then
                    If Not OutputPDF Is Nothing Then
                        OutputPDF.Close()
                        OutputPDF.Dispose()
                    End If

                    If Not PDFStream Is Nothing Then
                        PDFStream.Close()
                        PDFStream.Dispose()
                    End If

                    If Not Copier Is Nothing Then
                        Copier.Close()
                        Copier.Dispose()
                    End If

                    System.IO.File.Delete(OutputFileName)
                End If

                Throw
            Catch other As Exception
                ResultFile = Nothing
                ContinueMerge = False

                If Errors Is Nothing Then
                    Errors = New List(Of String)
                End If

                Errors.Add("General Error: " & other.Message)

                If System.IO.File.Exists(OutputFileName) Then
                    If Not OutputPDF Is Nothing Then
                        OutputPDF.Close()
                        OutputPDF.Dispose()
                    End If

                    If Not PDFStream Is Nothing Then
                        PDFStream.Close()
                        PDFStream.Dispose()
                    End If

                    If Not Copier Is Nothing Then
                        Copier.Close()
                        Copier.Dispose()
                    End If

                    System.IO.File.Delete(OutputFileName)
                End If

                Throw
            Finally
                If Not OutputPDF Is Nothing Then
                    OutputPDF.Close()
                    OutputPDF.Dispose()
                End If

                If Not PDFStream Is Nothing Then
                    PDFStream.Close()
                    PDFStream.Dispose()
                End If

                If Not Copier Is Nothing Then
                    Copier.Close()
                    Copier.Dispose()
                End If

                If System.IO.File.Exists(OutputFileName) Then
                    If ContinueMerge Then
                        ResultFile = New System.IO.FileInfo(OutputFileName)

                        If ResultFile.Length <= 0 Then
                            ResultFile = Nothing

                            Try
                                System.IO.File.Delete(OutputFileName)
                            Catch ex As Exception
                                Throw
                            End Try
                        End If
                    Else
                        ResultFile = Nothing

                        Try
                            System.IO.File.Delete(OutputFileName)
                        Catch ex As Exception
                            Throw
                        End Try
                    End If
                Else
                    ResultFile = Nothing
                End If
            End Try
        End If

        Return ResultFile
    End Function
#End Region
#End Region

#Region "PRIVATE METHODS"
    ''' <summary>
    ''' Recursive method to find all PDF files in subfolders of a given directory and
    ''' add them to the original list for merging
    ''' </summary>
    ''' <param name="RootFolder">The DirectoryInfo object in which to look for sub-directories containing additional PDF files to merge</param>
    ''' <returns>A list of all the PDF files found in all subfolders of the root</returns>
    Private Function GetFilesFromSubFoldersForMerge(ByVal RootFolder As System.IO.DirectoryInfo) As List(Of System.IO.FileInfo)
        Dim PDFFiles As New List(Of System.IO.FileInfo)

        For Each PDFSubFolder As System.IO.DirectoryInfo In RootFolder.GetDirectories
            For Each PDF As System.IO.FileInfo In PDFSubFolder.GetFiles("*.pdf")
                If PDF.Length > 0 Then
                    PDFFiles.Add(PDF)
                Else
                    If Warnings Is Nothing Then
                        Warnings = New List(Of String)
                    End If

                    Warnings.Add("Empty file : " & PDF.FullName & " not merged into output file.")
                End If
            Next PDF

            PDFFiles.AddRange(GetFilesFromSubFoldersForMerge(PDFSubFolder))
        Next PDFSubFolder

        Return PDFFiles
    End Function
#End Region
End Class

I'm hoping to add some methods for splitting PDF's, adding watermarks, etc. at some point in the future, but for now, I'm just trying to get the current functionality shored up the best I can. Of course, any comments or suggestions about anything in the code are absolutely welcome, regardless of whether or not it has specifically to do with error handling.


EDIT: I've added a couple of extra Catch conditions to the "master" Merge method (in the inner Try) that I believe may be useful, although I haven't been able to specifically test for those conditions yet. I added the following:

  • iTextSharp.text.pdf.BadPdfFormatException
  • iTextSharp.text.exceptions.BadPasswordException

I also added some Dispose calls in some of the other exception handling in the outer Try...Catch block before attempting to delete the output PDF file to make sure it isn't in use.

Additionally, I added a small block of code to add a Warning if the original PDF is repaired before being added to the output file.

\$\endgroup\$
  • \$\begingroup\$ Suggestion for a new tag(s): iText with a tag synonym of iTextSharp \$\endgroup\$ – G_Hosa_Phat Feb 4 '18 at 21:56
  • 1
    \$\begingroup\$ I've rolled the changes back. Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Zeta Feb 7 '18 at 16:22
  • \$\begingroup\$ Thank you, and I apologize as I hadn't read that Meta post. My intent was simply to share the "cleaned up" version of the code. \$\endgroup\$ – G_Hosa_Phat Feb 7 '18 at 16:43
2
\$\begingroup\$

Only focusing on the "monster" method Merge().

The usage of ContinueMerge seems senseless because this is set only to False in a Catch block where later the exception is thrown up the call stack. The only exception from this pattern is for catching the OtherEx which by the way is named poorly. For the case that ContinueMerge is False the loop where you caught the exception just isn't doing anything anymore hence you could Exit For or just return from the method.

But the biggest improvement would be if you could get rid of the repeated code targeting your List<T>'s

If Errors Is Nothing Then
    Errors = New List(Of String)
End If

By newing them in the constructor you could omit a lot of code. You have this pattern 11 times in your code which makes 33 lines of code.


What sense does it have to first apply a SortOrder and afterwards check if you can create the output directory? A lot of computing time may be lost if you can't create the directory.


Catching an exception which you just Throw doesn't add anything useful. E.g here

Try
    System.IO.File.Delete(OutputFileName)
Catch ex As Exception
    Throw
End Try

you could just remove the Try..Catch which would have the same result.


By using a Using statement for the objects

Dim OutputPDF As iTextSharp.text.Document = Nothing
Dim Copier As iTextSharp.text.pdf.PdfCopy = Nothing
Dim PDFStream As System.IO.FileStream = Nothing  

you could save a lot of code as well. Right now you Close() and Dispose() these objects at 3 places which wouldn't be necessary at all by using a Using statement. A Using statement basically is just a Try..Finally where the disposable object will be disposed in the Finally part. In addition, a call to Dispose() will, in good coded objects, always close any ressources hence a call to Close() won't be needed.

\$\endgroup\$
  • \$\begingroup\$ Excellent suggestions. Thank you so much for your help. As I said in my other question, I'm entirely self-taught, so, even though I've been programming for several years, I've probably gotten into some habits and patterns (e.g., not using the Using statement, etc.). I'll look into revising the method(s) with your suggestions. \$\endgroup\$ – G_Hosa_Phat Feb 5 '18 at 7:50
1
\$\begingroup\$

I've modified the "master" method to incorporate the suggestions provided by @Heslacher. I had been concerned about the fact that the overloaded methods didn't have their own Try...Catch blocks around the calls to the "master" method, but after testing with and without them, I now understand that, as long as the original calling method has it, any exceptions will "bubble-up " through the overloads as expected/intended.

Additionally, by testing the exception handling, I found a couple of places in my original code where I was incorrectly passing a "default" value into the "master" method instead of using the value provided by the overload. I've corrected these occurrences, and everything should be "cleaned up". In the spirit of sharing, here is the updated code:

''' <summary>
''' Class for manipulating PDF files
''' </summary>
Public Class PDFManipulator
#Region "PUBLIC PROPERTIES"
    Public Property Warnings As List(Of String)
    Public Property Errors As List(Of String)
#End Region

#Region "PDF MERGE METHODS"
    ''' <summary>
    ''' The file property to use for sorting the files before merging
    ''' </summary>
    Public Enum PDFMergeSortOrder
        Original
        FileDate
        FileName
        FileNameWithDirectory
    End Enum

#Region "PUBLIC METHODS"
#Region "CONSTRUCTOR"
    Public Sub New()
        Errors = New List(Of String)
        Warnings = New List(Of String)
    End Sub
#End Region

#Region "OVERLOAD METHODS"
    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, False, OutputFileName, False, PDFMergeSortOrder.Original, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, UseSmartMerge, OutputFileName, False, PDFMergeSortOrder.Original, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, False, OutputFileName, False, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, UseSmartMerge, OutputFileName, False, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, False, OutputFileName, False, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, UseSmartMerge, OutputFileName, False, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, False, OutputFileName, False, SortOrder, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <c>Nothing</c> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, UseSmartMerge, OutputFileName, False, SortOrder, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, False, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, UseSmartMerge, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFilePath, False, OutputFileName, OverwriteExistingPDF, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFilePath">The path in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified path for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFilePath As String, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Dim PDFFiles As New List(Of System.IO.FileInfo)
        Dim PDFFolder As New System.IO.DirectoryInfo(PDFFilePath)

        For Each PDF As System.IO.FileInfo In PDFFolder.GetFiles("*.pdf")
            If PDF.Length > 0 Then
                PDFFiles.Add(PDF)
            Else
                Warnings.Add("Empty file : " & PDF.FullName & " not merged into output file.")
            End If
        Next PDF

        If RecurseSubFolders Then
            PDFFiles.AddRange(GetFilesFromSubFoldersForMerge(PDFFolder))
        End If

        Return Merge(PDFFiles, UseSmartMerge, OutputFileName, OverwriteExistingPDF, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, False, OutputFileName, False, PDFMergeSortOrder.Original, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, UseSmartMerge, OutputFileName, False, PDFMergeSortOrder.Original, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, False, OutputFileName, False, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, UseSmartMerge, OutputFileName, False, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, False, OutputFileName, False, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, UseSmartMerge, OutputFileName, False, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, False, OutputFileName, False, SortOrder, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, UseSmartMerge, OutputFileName, False, SortOrder, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, False, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, UseSmartMerge, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original, RecurseSubFolders)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, False, OutputFileName, OverwriteExistingPDF, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return MergeAll(PDFFileDirectory, UseSmartMerge, OutputFileName, OverwriteExistingPDF, SortOrder, False)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileDirectory">The Directory in which to search for PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <param name="RecurseSubFolders">Identifies whether or not to look in subfolders of the specified Directory for additional PDF files</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function MergeAll(ByVal PDFFileDirectory As System.IO.DirectoryInfo, ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder, ByVal RecurseSubFolders As Boolean) As System.IO.FileInfo
        Dim PDFFiles As New List(Of System.IO.FileInfo)

        For Each PDF As System.IO.FileInfo In PDFFileDirectory.GetFiles("*.pdf").OrderBy(Function(f As System.IO.FileInfo) f.LastWriteTime)
            If PDF.Length > 0 Then
                PDFFiles.Add(PDF)
            Else
                Warnings.Add("Empty file : " & PDF.FullName & " not merged into output file.")
            End If
        Next PDF

        If RecurseSubFolders Then
            PDFFiles.AddRange(GetFilesFromSubFoldersForMerge(PDFFileDirectory))
        End If

        Return Merge(PDFFiles, UseSmartMerge, OutputFileName, OverwriteExistingPDF, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileNames">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFileNames As List(Of String), ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return Merge(PDFFileNames, False, OutputFileName, False, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileNames">A list of specific PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function Merge(ByVal PDFFileNames As List(Of String), ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return Merge(PDFFileNames, UseSmartMerge, OutputFileName, False, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileNames">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFileNames As List(Of String), ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean) As System.IO.FileInfo
        Return Merge(PDFFileNames, False, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileNames">A list of specific PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function Merge(ByVal PDFFileNames As List(Of String), ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean) As System.IO.FileInfo
        Return Merge(PDFFileNames, UseSmartMerge, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFileNames">A list of specific PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function Merge(ByVal PDFFileNames As List(Of String), ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Dim PDFFiles As New List(Of System.IO.FileInfo)

        For Each PDFName As String In PDFFileNames
            If System.IO.File.Exists(PDFName) Then
                Dim PDF As New System.IO.FileInfo(PDFName)

                If PDF.Length > 0 Then
                    PDFFiles.Add(PDF)
                Else
                    Warnings.Add("Empty file : " & PDF.FullName & " not merged into output file.")
                End If
            End If
        Next PDFName

        Return Merge(PDFFiles, UseSmartMerge, OutputFileName, OverwriteExistingPDF, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFiles">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFiles As List(Of System.IO.FileInfo), ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean) As System.IO.FileInfo
        Return Merge(PDFFiles, False, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFiles">A list of specific PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function Merge(ByVal PDFFiles As List(Of System.IO.FileInfo), ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean) As System.IO.FileInfo
        Return Merge(PDFFiles, UseSmartMerge, OutputFileName, OverwriteExistingPDF, PDFMergeSortOrder.Original)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFiles">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    Public Overloads Function Merge(ByVal PDFFiles As List(Of System.IO.FileInfo), ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return Merge(PDFFiles, False, OutputFileName, False, SortOrder)
    End Function

    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFiles">A list of specific PDF files to merge</param>
    ''' <param name="UseSmartMerge">Identifies whether to use a regular <see cref="iTextSharp.text.pdf.PdfCopy"/> or the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> for merging</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function Merge(ByVal PDFFiles As List(Of System.IO.FileInfo), ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Return Merge(PDFFiles, UseSmartMerge, OutputFileName, False, SortOrder)
    End Function
#End Region

#Region "MASTER MERGE METHOD"
    ''' <summary>
    ''' Merges multiple PDF files into a single PDF file
    ''' </summary>
    ''' <param name="PDFFiles">A list of specific PDF files to merge</param>
    ''' <param name="OutputFileName">The PDF file to create from the merged PDF files</param>
    ''' <param name="OverwriteExistingPDF">If the specified PDF file already exists, identifies whether or not to overwrite the existing file</param>
    ''' <param name="SortOrder">Identifies the order in which to add the source PDF files to the output file</param>
    ''' <returns>A FileInfo object representing the merged PDF if successful. <cref>Nothing</cref> if unsuccessful.</returns>
    ''' <remarks>Using the <see cref="iTextSharp.text.pdf.PdfCopy"/> (<paramref name="UseSmartMerge"/> = <c>False</c>) may result in larger files, 
    ''' while using the <see cref="iTextSharp.text.pdf.PdfSmartCopy"/> (<paramref name="UseSmartMerge"/> = <c>True</c>) may result in longer processing times.</remarks>
    Public Overloads Function Merge(ByVal PDFFiles As List(Of System.IO.FileInfo), ByVal UseSmartMerge As Boolean, ByVal OutputFileName As String, ByVal OverwriteExistingPDF As Boolean, ByVal SortOrder As PDFMergeSortOrder) As System.IO.FileInfo
        Dim ResultFile As System.IO.FileInfo = Nothing
        Dim SortedList As New List(Of System.IO.FileInfo)
        Dim PDFCopier As iTextSharp.text.pdf.PdfCopy = Nothing
        Dim SmartPDFCopier As iTextSharp.text.pdf.PdfSmartCopy = Nothing

        If OverwriteExistingPDF Then
            If System.IO.File.Exists(OutputFileName) Then
                Try
                    System.IO.File.Delete(OutputFileName)
                Catch ex As Exception
                    Errors.Add("Could not delete existing output file.")
                    Throw
                End Try
            End If
        ElseIf System.IO.File.Exists(OutputFileName) Then
            Errors.Add("Output file already exists.")
            Return Nothing
        End If

        If Not IO.Directory.Exists(New IO.FileInfo(OutputFileName).DirectoryName) Then
            Try
                IO.Directory.CreateDirectory(New IO.FileInfo(OutputFileName).DirectoryName)
            Catch ex As Exception
                Errors.Add("Could not create output directory.")
                Throw
            End Try
        End If

        Select Case SortOrder
            Case PDFMergeSortOrder.Original
                SortedList = PDFFiles
            Case PDFMergeSortOrder.FileDate
                SortedList = PDFFiles.OrderBy(Function(f As System.IO.FileInfo) f.LastWriteTime).ToList
            Case PDFMergeSortOrder.FileName
                SortedList = PDFFiles.OrderBy(Function(f As System.IO.FileInfo) f.Name).ToList
            Case PDFMergeSortOrder.FileNameWithDirectory
                SortedList = PDFFiles.OrderBy(Function(f As System.IO.FileInfo) f.FullName).ToList
        End Select

        Try
            Using OutputPDF As New iTextSharp.text.Document
                Using PDFStream As New System.IO.FileStream(OutputFileName, System.IO.FileMode.OpenOrCreate)
                    If UseSmartMerge Then
                        SmartPDFCopier = New iTextSharp.text.pdf.PdfSmartCopy(OutputPDF, PDFStream)
                    Else
                        PDFCopier = New iTextSharp.text.pdf.PdfCopy(OutputPDF, PDFStream)
                    End If

                    OutputPDF.Open()

                    For Each PDF As System.IO.FileInfo In SortedList
                        Try
                            Using InputReader As New iTextSharp.text.pdf.PdfReader(PDF.FullName)
                                For Page As Integer = 1 To InputReader.NumberOfPages
                                    If UseSmartMerge Then
                                        SmartPDFCopier.AddPage(SmartPDFCopier.GetImportedPage(InputReader, Page))
                                    Else
                                        PDFCopier.AddPage(PDFCopier.GetImportedPage(InputReader, Page))
                                    End If
                                Next Page

                                If InputReader.IsRebuilt Then
                                    Warnings.Add("Damaged PDF: " & PDF.FullName & " repaired and successfully merged into output file.")
                                End If
                            End Using
                        Catch InvalidEx As iTextSharp.text.exceptions.InvalidPdfException
                            'Skip this file
                            Errors.Add("Invalid PDF: " & PDF.FullName & " not merged into output file.")
                        Catch FormatEx As iTextSharp.text.pdf.BadPdfFormatException
                            'Skip this file
                            Errors.Add("Bad PDF Format: " & PDF.FullName & " not merged into output file.")
                        Catch PassworddEx As iTextSharp.text.exceptions.BadPasswordException
                            'Skip this file
                            Errors.Add("Password-protected PDF: " & PDF.FullName & " not merged into output file.")
                        Catch OtherEx As Exception
                            ResultFile = Nothing
                            Errors.Add("Other Error: " & OtherEx.Message & vbCrLf & vbCrLf & PDF.FullName & " not merged into output file.")

                            If System.IO.File.Exists(OutputFileName) Then
                                System.IO.File.Delete(OutputFileName)
                            End If

                            Exit For
                        End Try
                    Next PDF
                End Using
            End Using
        Catch ex As iTextSharp.text.pdf.PdfException
            ResultFile = Nothing
            Errors.Add("iTextSharp Error: " & ex.Message)

            If Not PDFCopier Is Nothing Then
                PDFCopier.Dispose()
            End If

            If Not SmartPDFCopier Is Nothing Then
                SmartPDFCopier.Dispose()
            End If

            If System.IO.File.Exists(OutputFileName) Then
                System.IO.File.Delete(OutputFileName)
            End If

            Throw
        Catch other As Exception
            ResultFile = Nothing
            Errors.Add("Other Error: " & other.Message)

            If Not PDFCopier Is Nothing Then
                PDFCopier.Dispose()
            End If

            If Not SmartPDFCopier Is Nothing Then
                SmartPDFCopier.Dispose()
            End If

            If System.IO.File.Exists(OutputFileName) Then
                System.IO.File.Delete(OutputFileName)
            End If

            Throw
        Finally
            If Not PDFCopier Is Nothing Then
                PDFCopier.Dispose()
            End If

            If Not SmartPDFCopier Is Nothing Then
                SmartPDFCopier.Dispose()
            End If

            If System.IO.File.Exists(OutputFileName) Then
                ResultFile = New System.IO.FileInfo(OutputFileName)

                If ResultFile.Length <= 0 Then
                    ResultFile = Nothing
                    System.IO.File.Delete(OutputFileName)
                End If
            Else
                ResultFile = Nothing
            End If
        End Try

        Return ResultFile
    End Function
#End Region
#End Region

#Region "PRIVATE METHODS"
    ''' <summary>
    ''' Recursive method to find all PDF files in subfolders of a given directory and
    ''' add them to the original list for merging
    ''' </summary>
    ''' <param name="RootFolder">The DirectoryInfo object in which to look for sub-directories containing additional PDF files to merge</param>
    ''' <returns>A list of all the PDF files found in all subfolders of the root</returns>
    Private Function GetFilesFromSubFoldersForMerge(ByVal RootFolder As System.IO.DirectoryInfo) As List(Of System.IO.FileInfo)
        Dim PDFFiles As New List(Of System.IO.FileInfo)

        For Each PDFSubFolder As System.IO.DirectoryInfo In RootFolder.GetDirectories
            For Each PDF As System.IO.FileInfo In PDFSubFolder.GetFiles("*.pdf")
                If PDF.Length > 0 Then
                    PDFFiles.Add(PDF)
                Else
                    Warnings.Add("Empty file : " & PDF.FullName & " not merged into output file.")
                End If
            Next PDF

            PDFFiles.AddRange(GetFilesFromSubFoldersForMerge(PDFSubFolder))
        Next PDFSubFolder

        Return PDFFiles
    End Function
#End Region
#End Region
End Class

EDIT: I found one other little "gotcha" in the "master" method. If the OverwriteExistingPDF parameter is set to False and the file specified by the OutputFileName already exists, I needed to exit the method. I'm not sure if it would be better to generate a new exception object and try to throw that back to the calling method, but, for now I've simply set it to return Nothing. Updated the code in this CW answer to reflect the change.

\$\endgroup\$
  • \$\begingroup\$ Thank you for converting it to a CW post. I wasn't sure if I should or not, so I appreciate the moderator intervention. :) \$\endgroup\$ – G_Hosa_Phat Feb 7 '18 at 20:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.