Concatenating PDFs with F#

At work today I needed to combine some pdfs together. Since I didn't have a license to Adobe Acrobat, nor MacOS (which has this functionality built in natively), I needed a quick solution using a third party library, in this case I used PdfSharp. I also figured a quick F# script would do the trick. So below is what I whipped up in about 20 minutes to get the job done and have a reuseable solution in the future if the need arises.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
30: 
31: 
32: 
33: 
34: 
35: 
36: 
#r "C:/Shared Resources/PdfSharp/PDFsharp.1.50.5147/lib/net20/PdfSharp.dll"

open System.IO
open PdfSharp.Pdf
open PdfSharp.Pdf.IO

let sourceDir = "C:/Users/Jason/Documents" // Or wherever your PDFs are
let targetFileName = Path.Combine(sourceDir, "Result.pdf") // Or whatever you want your output called
let pdfFiles = Directory.GetFiles(sourceDir, "*.pdf") |> Array.toList

// Gets all of the pages for a PDF document
let getPages (sourceFileName : string) =
    use source = PdfReader.Open(sourceFileName, PdfDocumentOpenMode.Import)
    [
      for i = 0 to source.PageCount - 1 do
        yield source.Pages.[i]
    ]

// Gets all of the pages for all of the PDFs
let getAllPages =
    List.collect getPages pdfFiles

// Adds all the pages to one document
let createDocFromPages pages =
    let targetDoc = new PdfDocument()  
    pages |> List.iter (targetDoc.Pages.Add >> ignore)
    targetDoc

// Creates the document and writes output to disk
let concatDocs =  
    let doc = createDocFromPages getAllPages
    doc.Save targetFileName
    doc.Dispose()

// Kicks it all off
concatDocs
namespace System
namespace System.IO
namespace PdfSharp
namespace PdfSharp.Pdf
namespace PdfSharp.Pdf.IO
val sourceDir : string

Full name: concatenatingpdfswithfsharp.sourceDir
val targetFileName : string

Full name: concatenatingpdfswithfsharp.targetFileName
type Path =
  static val DirectorySeparatorChar : char
  static val AltDirectorySeparatorChar : char
  static val VolumeSeparatorChar : char
  static val InvalidPathChars : char[]
  static val PathSeparator : char
  static member ChangeExtension : path:string * extension:string -> string
  static member Combine : [<ParamArray>] paths:string[] -> string + 3 overloads
  static member GetDirectoryName : path:string -> string
  static member GetExtension : path:string -> string
  static member GetFileName : path:string -> string
  ...

Full name: System.IO.Path
Path.Combine([<System.ParamArray>] paths: string []) : string
Path.Combine(path1: string, path2: string) : string
Path.Combine(path1: string, path2: string, path3: string) : string
Path.Combine(path1: string, path2: string, path3: string, path4: string) : string
val pdfFiles : string list

Full name: concatenatingpdfswithfsharp.pdfFiles
type Directory =
  static member CreateDirectory : path:string -> DirectoryInfo + 1 overload
  static member Delete : path:string -> unit + 1 overload
  static member EnumerateDirectories : path:string -> IEnumerable<string> + 2 overloads
  static member EnumerateFileSystemEntries : path:string -> IEnumerable<string> + 2 overloads
  static member EnumerateFiles : path:string -> IEnumerable<string> + 2 overloads
  static member Exists : path:string -> bool
  static member GetAccessControl : path:string -> DirectorySecurity + 1 overload
  static member GetCreationTime : path:string -> DateTime
  static member GetCreationTimeUtc : path:string -> DateTime
  static member GetCurrentDirectory : unit -> string
  ...

Full name: System.IO.Directory
Directory.GetFiles(path: string) : string []
Directory.GetFiles(path: string, searchPattern: string) : string []
Directory.GetFiles(path: string, searchPattern: string, searchOption: SearchOption) : string []
module Array

from Microsoft.FSharp.Collections
val toList : array:'T [] -> 'T list

Full name: Microsoft.FSharp.Collections.Array.toList
val getPages : sourceFileName:string -> PdfPage list

Full name: concatenatingpdfswithfsharp.getPages
val sourceFileName : string
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
val source : PdfDocument
type PdfReader =
  static member Open : path:string -> PdfDocument + 10 overloads
  static member TestPdfFile : path:string -> int + 2 overloads

Full name: PdfSharp.Pdf.IO.PdfReader
PdfReader.Open(stream: Stream) : PdfDocument
   (+0 other overloads)
PdfReader.Open(path: string) : PdfDocument
   (+0 other overloads)
PdfReader.Open(stream: Stream, openmode: PdfDocumentOpenMode) : PdfDocument
   (+0 other overloads)
PdfReader.Open(path: string, password: string) : PdfDocument
   (+0 other overloads)
PdfReader.Open(path: string, openmode: PdfDocumentOpenMode) : PdfDocument
   (+0 other overloads)
PdfReader.Open(stream: Stream, password: string, openmode: PdfDocumentOpenMode) : PdfDocument
   (+0 other overloads)
PdfReader.Open(stream: Stream, openmode: PdfDocumentOpenMode, passwordProvider: PdfPasswordProvider) : PdfDocument
   (+0 other overloads)
PdfReader.Open(path: string, password: string, openmode: PdfDocumentOpenMode) : PdfDocument
   (+0 other overloads)
PdfReader.Open(path: string, openmode: PdfDocumentOpenMode, provider: PdfPasswordProvider) : PdfDocument
   (+0 other overloads)
PdfReader.Open(stream: Stream, password: string, openmode: PdfDocumentOpenMode, passwordProvider: PdfPasswordProvider) : PdfDocument
   (+0 other overloads)
type PdfDocumentOpenMode =
  | Modify = 0
  | Import = 1
  | ReadOnly = 2
  | InformationOnly = 3

Full name: PdfSharp.Pdf.IO.PdfDocumentOpenMode
field PdfDocumentOpenMode.Import = 1
val i : int
property PdfDocument.PageCount: int
property PdfDocument.Pages: PdfPages
val getAllPages : PdfPage list

Full name: concatenatingpdfswithfsharp.getAllPages
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  interface IEnumerable
  interface IEnumerable<'T>
  member GetSlice : startIndex:int option * endIndex:int option -> 'T list
  member Head : 'T
  member IsEmpty : bool
  member Item : index:int -> 'T with get
  member Length : int
  member Tail : 'T list
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
val collect : mapping:('T -> 'U list) -> list:'T list -> 'U list

Full name: Microsoft.FSharp.Collections.List.collect
val createDocFromPages : pages:PdfPage list -> PdfDocument

Full name: concatenatingpdfswithfsharp.createDocFromPages
val pages : PdfPage list
val targetDoc : PdfDocument
Multiple items
type PdfDocument =
  inherit PdfObject
  new : unit -> PdfDocument + 2 overloads
  member AcroForm : PdfAcroForm
  member AddPage : unit -> PdfPage + 1 overload
  member CanSave : message:string -> bool
  member Close : unit -> unit
  member CustomValues : PdfCustomValues with get, set
  member Dispose : unit -> unit
  member FileSize : int64
  member Flatten : unit -> unit
  member FullPath : string
  ...

Full name: PdfSharp.Pdf.PdfDocument

--------------------
PdfDocument() : unit
PdfDocument(filename: string) : unit
PdfDocument(outputStream: Stream) : unit
val iter : action:('T -> unit) -> list:'T list -> unit

Full name: Microsoft.FSharp.Collections.List.iter
PdfPages.Add() : PdfPage
PdfPages.Add(page: PdfPage) : PdfPage
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
val concatDocs : unit

Full name: concatenatingpdfswithfsharp.concatDocs
val doc : PdfDocument
PdfDocument.Save(stream: Stream) : unit
PdfDocument.Save(path: string) : unit
PdfDocument.Save(stream: Stream, closeStream: bool) : unit
PdfDocument.Dispose() : unit