I need to generate PDF from my Windows Phone app.

Unfortunately none of the standard free PDF-generating libraries work on Windows Phone. I've had to generate the PDF myself, by writing to the file format directly. It turned out to be really easy! Source code is at the bottom of this post, and in this link:

  •  Download source code.zip [60k, requires VS2012, includes Console and WP8 apps]

 

Why even generate PDF on Phone?

Some people say that there's no point having PDFs on the phone, since it's not the most efficient use of screen and touch interaction.

Well in my case, I wanted to generate a PDF, upload it to the user's SkyDrive account, and let the user send an email with a "share" link. That's how Microsoft's own PDF-Reader app works when you click the "share" button.

Other people suggest that a Phone has no business generating PDFs because it's such a costly operation, and you should instead upload your document to your PDF-generating webserver. This is wrong. PDF-generation, at least for the kinds of reports I was generating (two pages of non-flowed text), is a very cheap operation. I bet that it costs more battery to upload+download a document than to generate a PDF.

iTextSharp

http://itextpdf.com/ (AGPL license, or you can purchase a commercial license, available on NuGet)

iTextSharp doesn't work on Windows Phone, because it has a runtime dependency on System.Drawing.dll. (There's another experimental port http://itextsharpsl.codeplex.com/ under the MPL license, designed to work on Silverlight and Windows Phone, but I was unable to figure out the references needed to get it work. It's also a huge package, 30mb compressed source code). iTextSharp has superb documentation, including a published book. Here's how its API looks, to generate the PDF at the top of this article:

Imports  iTextSharp.text 

Module  Module1
     Sub  Main()
         Dim  doc  As  New  Document ()
         Using  file  As  New  System.IO. FileStream ( "a.pdf" , System.IO. FileMode .Create),
            writer = pdf. PdfWriter .GetInstance(doc, file)
            doc.Open()

             Dim  titleFont =  FontFactory .GetFont( FontFactory .TIMES, 40,  Font .NORMAL)
             Dim  subtitleFont =  FontFactory .GetFont( FontFactory .TIMES, 20,  Font .ITALIC,  BaseColor .BLUE)
             Dim  column  As  New  pdf. ColumnText (writer.DirectContent)
            column.SetSimpleColumn(230, 600, 400, 400)  ' bounding coords of column
            column.AddText( New  Phrase ( "Hello all" , titleFont))
            column.Go()
            column.AddText( New  Phrase ( "olé" , subtitleFont))
            column.Go()

 
          doc.Close()
         End  Using
         Process .Start( "a.pdf" )
     End  Sub
End  Module

 

PDFSharp

http://www.pdfsharp.net/ (MIT license, available on NuGet)

PDFSharp doesn't work on Windows Phone, because it has a compiletime dependency on System.Drawing.dll. Here's how its API looks...

Imports  PdfSharp.Drawing

 Module  Module1 
   
 Sub  Main()
       
 Dim  document  As  New  PdfSharp.Pdf. PdfDocument 
       
 Dim  page = document.AddPage
       
 Dim  gfx =  XGraphics .FromPdfPage(page)
       
 Dim  titleFont  As  New  XFont ( "Times New Roman" , 40,  XFontStyle .Regular)
       
 Dim  subtitleFont  As  New  XFont ( "Times New Roman" , 20,  XFontStyle .Italic)
        gfx.DrawString(
 "Hello all" , titleFont,  XBrushes .Black, 230, 400)
       
 Dim  height = gfx.MeasureString( "olé" , subtitleFont).Height
        gfx.DrawString(
 "olé" , subtitleFont,  XBrushes .Blue, 230, 400 + height)

       
 Using  stream  As  New  IO. FileStream ( "a.pdf" , IO. FileMode .Create)
            document.Save(stream)
       
 End  Using 

       
 Process .Start( "a.pdf" )
   
 End  Sub 
 End  Module

 

PDFJet

http://www.pdfjet.com/os/edition.html (BSD license; not available on NuGet)

PDFJet doesn't work on Windows Phone, because it has a compiletime dependency System.IO.DeflateStream. PDFJet is the most interesting of all these PDF libraries. It's been written from scratch with as few dependencies as possible - no dependencies on System.Drawing, or Winforms. Its only dependency is on DeflateStream. If someone could package it up with a Phone-compatible implementation of DeflateStream then it would work great. I tried to get it to work with DotNetZip and SharpZipLib but didn't succeed. Note that PDFJet is distributed as just a directory full of .cs files, so you have to create a project for it. Easy to do. Anyway, here's how it looks:

Imports  PDFjet.NET

 Module  Module1 

   
 Sub  Main()
       
 Using  stream  As  New  IO. FileStream ( "a.pdf" , IO. FileMode .Create)
           
 Dim  pdf  As  New  PDF (stream)
           
 Dim  page  As  New  Page (pdf,  Letter .PORTRAIT)
           
 Dim  titleFont  As  New  Font (pdf,  CoreFont .TIMES_ROMAN)
            titleFont.SetSize(40)
           
 Dim  subtitleFont  As  New  Font (pdf,  CoreFont .TIMES_ITALIC)
            subtitleFont.SetSize(20)
           
 Dim  text1  As  New  TextLine (titleFont,  "Hello all" )
            text1.SetPosition(230, 400)
            text1.DrawOn(page)
           
 Dim  text2  As  New  TextLine (subtitleFont,  "olé" )
            text2.SetPosition(230, 400 + text2.GetHeight)
            text2.SetColor(
 Color .blue)
            text2.DrawOn(page)
            pdf.Flush()
       
 End  Using 

       
 Process .Start( "a.pdf" )
   
 End  Sub 

 End  Module 

 

PDFClown

http://pdfclown.wordpress.com/ (LGPL license; not available on NuGet)

PDFClown doesn't work on Windows Phone, because it has a compiletime dependency System.Drawing and Winforms. PDFClown aggressively uses Java rather than .NET conventions. Its API is an exact mirror of the PDF file format structure. In my view it doesn't offer much benefit beyond just writing the file format structure manually. Here's how it looks:

Imports  org.pdfclown
Imports  org.pdfclown.documents.contents

 Module  Module1 

   
 Sub  Main()
       
 Using  stream  As  New  IO. FileStream ( "a.pdf" , IO. FileMode .Create),
              pdfstream
 As  New  org.pdfclown.bytes. Stream (stream),
              pdf
 As  New  files. File ()

           
 Dim  document = pdf.Document
           
 Dim  page  As  New  documents. Page (document)
            document.Pages.Add(page)
           
 Dim  titleFont =  New  fonts. StandardType1Font (document, fonts. StandardType1Font . FamilyEnum .Times,  False ,  False )
           
 Dim  subtitleFont =  New  fonts. StandardType1Font (document, fonts. StandardType1Font . FamilyEnum .Times,  False ,  True )
           
 Dim  composer  As  New  composition. PrimitiveComposer (page)
           
 Dim  blockcomposer  As  New  composition. BlockComposer (composer)
            blockcomposer.Begin(
 New  Drawing. RectangleF (230, 400, 200, 200), composition. XAlignmentEnum .Left, composition. YAlignmentEnum .Top)
            composer.SetFont(titleFont, 40)
            blockcomposer.ShowText(
 "Hello all"  & vbCrLf)
            composer.SetFont(subtitleFont, 20)
            composer.SetFillColor(
 New  colorSpaces. DeviceRGBColor (0, 0, 1))
            blockcomposer.ShowText(
 "olé"  & vbCrLf)
            blockcomposer.End()
            composer.Flush()

            pdf.Save(pdfstream, files.
 SerializationModeEnum .Standard)
       
 End  Using 

       
 Process .Start( "a.pdf" )

   
 End  Sub 

 End  Module 

 

Write the PDF file format yourself

http://acroeng.adobe.com/wp/?page_id=321 (documentation of PDF file format; I used the 1.2 version of the PDF spec)

This code works great on Windows Phone. Frankly, the PDF file format is very simple if all you want to do is create simple reports - with text, charts &c. What we need is sample code for that show how to create PDFs, not just libraries that encapsulate how to create PDFs. So here's my sample code! Note that I make use of one extra function "enc" which translates non-ASCII from Unicode (used in .NET) into WinAnsiEncoding (the encoding I chose to use for the PDF) - full source code is downloadable from the link at the top of this article.

 

Private  Async  Sub  Button1_Click(sender  As  Object , e  As  RoutedEventArgs )  Handles  Button1.Click

   
 Dim  file =  Await ApplicationData .Current.LocalFolder.CreateFileAsync("a.pdf" , Windows.Storage. CreationCollisionOption .ReplaceExisting)
   
 Using  stream =  Await  System.IO. WindowsRuntimeStorageExtensions .OpenStreamForWriteAsync(file),
          writer
 As  New  IO. StreamWriter (stream, Text. Encoding .UTF8)

       
 Dim  xrefs  As  New  List ( Of  Long )()

       
 ' PDF-HEADER 
        writer.WriteLine(
 "%PDF-1.2" )

       
 ' PDF-BODY. Convention is to start with a 4-byte binary comment 
       
 ' so everyone recognizes the pdf as binary. Then the file has 
       
 ' a load of numbered objects, #1..#7 in this case 
        writer.Write(
 "%" ) : writer.Flush()
        stream.Write({&HC7, &HEC, &H8F, &HA2}, 0, 4) : stream.Flush()
        writer.WriteLine(
 "" )

       
 ' #1: catalog - the overall container of the entire PDF 
        writer.Flush() : stream.Flush() : xrefs.Add(stream.Position)
        writer.WriteLine(
 "1 0 obj" )
        writer.WriteLine(
 "<<" )
        writer.WriteLine(
 "  /Type /Catalog" )
        writer.WriteLine(
 "  /Pages 2 0 R" )
        writer.WriteLine(
 ">>" )
        writer.WriteLine(
 "endobj" )

       
 ' #2: page-list - we have only one child page 
        writer.Flush() : stream.Flush() : xrefs.Add(stream.Position)
        writer.WriteLine(
 "2 0 obj" )
        writer.WriteLine(
 "<<" )
        writer.WriteLine(
 "  /Type /Pages" )
        writer.WriteLine(
 "  /Kids [3 0 R]" )
        writer.WriteLine(
 "  /Count 1" )
        writer.WriteLine(
 ">>" )
        writer.WriteLine(
 "endobj" )

        
 ' #3: page - this is our page. We specify size, font resources, and the contents 
        writer.Flush() : stream.Flush() : xrefs.Add(stream.Position)
        writer.WriteLine(
 "3 0 obj" )
        writer.WriteLine(
 "<<" )
        writer.WriteLine(
 "  /Type /Page" )
        writer.WriteLine(
 "  /Parent 2 0 R" )
        writer.WriteLine(
 "  /MediaBox [0 0 612 792]" )  ' Default userspace units: 72/inch, origin at bottom left 
        writer.WriteLine(
 "  /Resources" )
        writer.WriteLine(
 "  <<" )
        writer.WriteLine(
 "    /ProcSet [/PDF/Text]" )  ' This PDF uses only the Text ability 
        writer.WriteLine(
 "    /Font" )
        writer.WriteLine(
 "    <<" )
        writer.WriteLine(
 "      /F0 4 0 R" )  ' I will define three fonts, #4, #5 and #6 
        writer.WriteLine(
 "      /F1 5 0 R" )
        writer.WriteLine(
 "      /F2 6 0 R" )
        writer.WriteLine(
 "    >>" )
        writer.WriteLine(
 "  >>" )
        writer.WriteLine(
 "  /Contents 7 0 R" )
        writer.WriteLine(
 ">>" )
        writer.WriteLine(
 "endobj" )

       
 ' #4, #5, #6: three font resources, all using fonts that are built into all PDF-viewers 
       
 ' We're going to use WinAnsi character encoding, defined below. 
        writer.Flush() : stream.Flush() : xrefs.Add(stream.Position)
        writer.WriteLine(
 "4 0 obj" )
        writer.WriteLine(
 "<<" )
        writer.WriteLine(
 "  /Type /Font" )
        writer.WriteLine(
 "  /Subtype /Type1" )
        writer.WriteLine(
 "  /Encoding /WinAnsiEncoding" )
        writer.WriteLine(
 "  /BaseFont /Times-Roman" )
        writer.WriteLine(
 ">>" )
        writer.Flush() : stream.Flush() : xrefs.Add(stream.Position)
        writer.WriteLine(
 "5 0 obj" )
        writer.WriteLine(
 "<<" )
        writer.WriteLine(
 "  /Type /Font" )
        writer.WriteLine(
 "  /Subtype /Type1" )
        writer.WriteLine(
 "  /Encoding /WinAnsiEncoding" )
        writer.WriteLine(
 "  /BaseFont /Times-Bold" )
        writer.WriteLine(
 ">>" )
        writer.Flush() : stream.Flush() : xrefs.Add(stream.Position)
        writer.WriteLine(
 "6 0 obj" )
        writer.WriteLine(
 "<<" )
        writer.WriteLine(
 "  /Type /Font" )
        writer.WriteLine(
 "  /Subtype /Type1" )
        writer.WriteLine(
 "  /Encoding /WinAnsiEncoding" )
        writer.WriteLine(
 "  /BaseFont /Times-Italic" )
        writer.WriteLine(
 ">>" )

       
 ' #7: contents of page. This is written in postscript, fully described in 
       
 ' chapter 8 of the PDF 1.2 reference manual. 
        writer.Flush() : stream.Flush() : xrefs.Add(stream.Position)
       
 Dim  sb  As  New  Text. StringBuilder 
        sb.AppendLine(
 "BT" )              ' BT = begin text object, with text-units the same as userspace-units 
        sb.AppendLine(
 "/F0 40 Tf" )       ' Tf = start using the named font "F0" with size "40" 
        sb.AppendLine(
 "40 TL" )           ' TL = set line height to "40" 
        sb.AppendLine(
 "230.0 400.0 Td" )  ' Td = position text point at coordinates "230.0", "400.0" 
        sb.AppendLine(
 "(Hello all) '"  ' Apostrophe = print the text, and advance to the next line 
        sb.AppendLine(
 "/F2 20 Tf" )       ' 
        sb.AppendLine(
 "20 TL" )           ' 
        sb.AppendLine(
 "0.0 0.2 1.0 rg" )  ' rg = set fill color to RGB("0.0", "0.2", "1.0") 
        sb.AppendLine(
 "(ol"  & enc( "é" ) &  ") '" )
        sb.AppendLine(
 "ET" )              ' 
       
 ' 
        writer.WriteLine(
 "7 0 obj" )
        writer.WriteLine(
 "<<" )
        writer.WriteLine(
 "  /Length "  & sb.Length)
        writer.WriteLine(
 ">>" )
        writer.WriteLine(
 "stream" )
        writer.Write(sb.ToString())
        writer.WriteLine(
 "endstream" )
        writer.WriteLine(
 "endobj" )

       
 ' PDF-XREFS. This part of the PDF is an index table into every object #1..#7 
       
 ' that we defined. 
        writer.Flush() : stream.Flush() :
 Dim  xref_pos = stream.Position
        writer.WriteLine(
 "xref" )
        writer.WriteLine(
 "1 "  & xrefs.Count)
       
 For  Each  xref  In  xrefs
            writer.WriteLine(
 "{0:0000000000} {1:00000} n" , xref, 0)
       
 Next 

       
 ' PDF-TRAILER. Every PDF ends with this trailer. 
        writer.WriteLine(
 "trailer" )
        writer.WriteLine(
 "<<" )
        writer.WriteLine(
 "  /Size "  & xrefs.Count)
        writer.WriteLine(
 "  /Root 1 0 R" )
        writer.WriteLine(
 ">>" )
        writer.WriteLine(
 "startxref" )
        writer.WriteLine(xref_pos)
        writer.WriteLine(
 "%%EOF" )
   
 End  Using 

   
 Await  Windows.System. Launcher .LaunchFileAsync(file)

 End  Sub