Filling and processing Adobe PDF Forms with iTextSharp
As i have blogged before, iTextSharp is a .NET port of iText Java-PDF library. Current version of iTextSharp 3.1.5 (2006-09-14) is based on iText 1.4.5 and among other cool things allows you to work with Adobe's FDF (Form Data Format) data. FDF is used by PDF forms created with the form tools in Adobe Acrobat. These PDF forms can be displayed and filled by users directly in web browser and then submited back to the server for further processing - for example flatten them to downloadable PDF file. This communication between client and server uses Adobe FDF protocol.
With iTextSharp you can easily create FDF file to prefill values in the form and also to process form submitted data back on the server. I am currently using this technique in one application, so i decided to drop a little demo project here. In this application advisors fill client's contract data directly to Adobe PDF form and submitting them back to system. The contract PDF form is prefilled with date, advisor's name, address and other data. When the advisor submits this form back to server, it's processed by ASP.NET, stored to database and finally flattened to classic downloadable PDF file.
This demo project is build for ASP.NET 2.0 and you will need iTextSharp library for it to work. It's made as simple as possible, and you can download PDFFormDemo.zip right now. It shows the basic steps to create FDF stream with prefilled data and to catch form submit back on the server.
The first file Default.aspx is used as a simple ASP.NET form to enter some user data (in this example it's first and last name). The form contains two simple text boxes and one submit button. Let's take a look at the submit button's onclick event...
1: private readonly string pdfFormFileName = "PDFForm.pdf";
2:
3: protected void OpenPDF_Click(object sender, EventArgs e)
4: {
5: Response.Clear();
6: Response.ContentType = "application/vnd.fdf";
7:
8: FdfWriter fdfWriter = new FdfWriter();
9:
10: fdfWriter.File = GetAbsolutePath() + pdfFormFileName;
11:
12: fdfWriter.SetFieldAsName("txtFirstName", FirstName.Text);
13: fdfWriter.SetFieldAsName("txtLastName", LastName.Text);
14:
15: Response.AddHeader("Content-disposition", "inline; filename=FlatPDFForm.fdf");
16: fdfWriter.WriteTo(Response.OutputStream);
17: Response.End();
18: }
This one is quite simple, we change response content type to application/vnd.fdf and use FdfWriter class to create FDF stream. First and Last name are stored to this stream (txtFirstName and txtLastName are the names of Adobe PDF Form fields). The File property of FdfWriter class associates this FDF file with our PDF form (PDFForm.pdf) and this file is opened in the client's browser (the path is abolute Url location to PDF file). We also set content disposition to open this file inline in the browser.
After this Onclick handler executes, PDF file opens in your browser. You now have prefilled values for FirstName and LastName in the PDF form and you can further change them so as to fill other form fields. After you click the form's submit button the data is submitted as FDF stream to the server (to the location hardcoded in PDF file - in our example ProcessFDF.ashx). The ProcessFDF.ashx file is HTTP handler file used to process the submitted data. Let's look at it's ProcessRequest method.
1: private readonly string pdfFormFileName = "PDFForm_print.pdf";
2:
3: public void ProcessRequest (HttpContext context) {
4: string contentType = context.Request.ContentType;
5:
6: if (String.Compare(contentType, "application/vnd.fdf", true) == 0)
7: {
8: ProcessFDFRequestFlattenPDF(context);
9: }
10: else
11: {
12: context.Response.ContentType = "text/plain";
13: context.Response.Write("Not a FDF request");
14: }
15: }
16:
17: private void ProcessFDFRequestFlattenPDF(HttpContext context)
18: {
19: MemoryStream pdfFlat = new MemoryStream();
20:
21: PdfReader pdfReader = new PdfReader(context.Request.MapPath(pdfFormFileName));
22: PdfStamper pdfStamper = new PdfStamper(pdfReader, pdfFlat);
23:
24: // bind fields from fdf...
25: FdfReader fdfReader = new FdfReader(context.Request.InputStream);
26:
27: AcroFields pdfForm = pdfStamper.AcroFields;
28: pdfForm.SetFields(fdfReader);
29:
30: pdfStamper.FormFlattening = true;
31: pdfStamper.Writer.CloseStream = false;
32: pdfStamper.Close();
33:
34: context.Response.ContentType = "application/pdf";
35: context.Response.AddHeader("Content-disposition", "attachment; filename=FlatPDFForm.pdf");
36:
37: pdfFlat.WriteTo(context.Response.OutputStream);
38: pdfFlat.Close();
39: }
The ProcessRequest method tests for correct submited content type and if it's set correctly to application/vnd.fdf processes this request. We just flatten this FDF stream with our PDF form file and send the flattened PDF file back to the client's browser. You may have noticed we are using another PDF form file here PDFForm_print.pdf. This file is basically the same as the one in previous step, only without TextBox black borders. The logic is quite straightforward, PdfReader is used to read PDF form file and PdfStamper created for this file. The stampers FormField collection is set to our submited FDF request and finally the PDF Form and FDF stream are merged together in one noneditable PDF file. This file is streamed back to the client's browser this time as an attachment.
As you can see this is quite easy. This technique works fine with Adobe Acrobat generated PDF Forms. There are some problems with iTextSharp and Adobe Distiler forms, because it generates some weird tags. This was only a demo, so please keep in mind real world application would need some additional erro checking and stuff like that.