Populating PDF Forms using ColdFusion and the Adobe FDF file format.

by Maxim Paperno <MPaperno@WorldDesign.com>, 11/14/00
All reproduction rights reserved.
 

[This information is pretty outdated. (13+ years? I'd say so!)  There's probably a much better way of doing all this now, but I haven't worked with FDF in a long time.  YMMV :)  -Max  1/10/14 ]

The objective is to deliver a PDF form on the fly with values pre-filled on the server side with dynamic data. I'll assume that to get the dynamic data, you'd execute a query, say qryGetData as an example. The data can really be from any source. Example CFML and FDF code is included below, while the sample PDF file can be downloaded from >here<. You can also run the example from >here<.

1) First you will need your PDF form. Create a regular PDF document, and using the full Acrobat (not the reader) "draw" your form fields in it. Give these form fields some meaningful names (I like to give them the same names as my CF variables to keep things clear).

2) For the form default values, put in CF variables! So for example you have a field for First Name-- for its value, in Acrobat, you could put in #qryGetData.FirstName# (the default value for a given field is found in its Properties/Options dialog). Don't forget the # signs. You can actually use ANY CF command/function within the field values.

3) From Acrobat, export your form definitions as an FDF file (File/Export/Form Data...). This creates a plain text file containing the field definitions and the default field values (ie, all your CF variables). Save the PDF too. The PDF will need to be accessible via a URL from your site, and the FDF will need to be accessible to CF Server (as a CFINCLUDE). For the example lets call them myForm.fdf and myForm.pdf.

4) Open up the FDF file in CF Studio (or your favorite plain text editor). You'll need to make a few minor adjustments here. The FDF format is very terse and is pretty easy to work with. You don't have to understand it's structure to do any of the following.

    a) Scroll down to the end and find the line that has '/F (myForm.pdf)>>' on it. Replace the part inside the parenthesis with a full URL which will point to the PDF template on your server. For example, the line might read like this (w/out the quotes): '/F (http://www.myServer.com/PDF/myForm.pdf)>>'

    b) Wrap CFOUTPUT tags around the entire contents of the FDF file.

    c) If you used any CF functions within your form field values (such as DollarFormat()) you will need to do a search/replace. Acrobat escapes all '(' and ')' by preceding them with a backslash (\). Replace '\(' with '(' and '\)' with ')' globally (this is very quick).

    d) Occasionally Acrobat will put a line break in the middle of your CF variable. You can find these later during debugging, and/or quickly eyeball the FDF file and look for line breaks in the middle of a variable. The line break will also be escaped with a '\' preceding the break. Remove the line break and the \. You can also double-check that there are no stray # signs while you're at it. If you have a large FDF, it's easier to fix this during debug since CF will tell you the exact line number of the error.

5) After you save the modified FDF file, you're ready for the CF part. It's pretty simple and there are probably several ways to do it.

Here's what I've done. You have your action template (or whatever), say act_returnPDF.cfm. First you want to make sure that the action which runs this template won't generate any other content besides the FDF file (so no other headers/footers/etc). Avoid white space too. A leading blank line or any other content in the results will screw things up.

6) In your action template, perform your query that returns the variable names you've used in the PDF/FDF templates, or otherwise assign values to the variables.

7) FDF uses the parenthesis characters as delimiters (see step 4c, above). If any of your content may have '(' or ')' in it, you need to escape these using '\' in front of them. See the sample code for an example.

8) Then, you basically just CFINCLUDE the FDF file. Any CFML and all CF variables are interpreted by CF and replaced with data. Since you want to send the FDF file to the browser as an FDF (not text/html), you can use CFCONTENT to return the output. Here's an actual example (also see the sample code below for a more complete example):

<CFHEADER NAME="Content-Disposition" VALUE="inline; filename=myForm.fdf">
<CFCONTENT TYPE="application/vnd.fdf">
<CFINCLUDE TEMPLATE="myForm.fdf">

The CFHEADER line sets the file name that will be sent (so IE knows it's an FDF file), while the CFCONTENT makes sure Netscape knows what to do (since it uses the MIME type like a good little browser should).

Note: To debug, comment out, or otherwise exclude, the CFHEADER and CFCONTENT tags. This will allow you to see the raw FDF file being returned, and will show any CF errors that might be thrown. Make sure you get a "clean" FDF file being returned before continuing. A clean FDF file will start with '%FDF' and end with '%%EOF'.

9) Then it gets a touch complicated to follow the flow. When the browser sees the FDF file type, it runs Acrobat (full or Reader) which opens the FDF file and sees the /F switch and the URL to the PDF template. Acrobat then requests the PDF file via that URL. Once Acrobat retrieves the template PDF, it performs the field substitutions, replacing the defined form fields in the PDF with the data that is specified in the FDF. The result is a PDF file with the data dynamically populated.

The full range of options for PDF documents applies, such as protecting from editing, selecting, printing, etc., etc.

Another cool feature of this is that you can have one FDF file which works for any number of PDF templates. As long as the fields are named the same across the PDFs, you can dynamically drive the URL in the /F switch in the FDF file to point to any PDF. For example you have the same basic form that is laid out differently in each State. With just one FDF file you can deliver any State's custom form using different PDF templates.

Active-X Method:
BTW, please note that there is a slightly different method for doing this using Adobe's ActiveX FDF component. This method might be preferable to the above if your form field definitions change often, since you can avoid mucking around in the FDF file altogether. There are probably other methods for accomplishing the same thing, using Adobe's FDF tools <http://partners.adobe.com/asn/developer/acrosdk/forms.html>

-------------------------
Code examples:
-------------------------

This is myForm.fdf:
(Note that it points to a PDF template that's located on my server. You can download/save the PDF to examine it, if you wish, via the URL specified.)

------ start code ------

<CFOUTPUT>%FDF-1.2
%âãÏÓ
1 0 obj
<<
/FDF << /Fields [ << /V (#variables.Body#)/T (Body)>> << /V (#variables.To#)/T (To)>>
]
/F (http://www.worlddesign.com/Content/rd/cf/pdf/myForm.pdf)>>
>>
endobj
trailer
<<
/Root 1 0 R

>>
%%EOF
</CFOUTPUT>

----- end code -----

This is returnPDF.cfm:

----- start code ------
<CFSETTING ENABLECFOUTPUTONLY="YES">

<!--- returnPDF.cfm, by Max Paperno 11/14/00 (MPaperno@WorldDesign.com)
Returns an Adobe FDF file to the browser. The FDF file contains form value data and a
   pointer to a template PDF file. The FDF file being used is called myForm.fdf. The end
   result is that the PDF template is displayed to the user using the form data dynamically
   populated in the FDF file.
See <http://partners.adobe.com/asn/developer/acrosdk/forms.html> for more information
   on Adobe's FDF format

--->

<!--- Set a debug flag. A value of 1 will help debug the FDF file if necessary. --->
<CFPARAM NAME="URL.Debug" DEFAULT="0">

<!--- Set values for the variables used in myForm.fdf.
This could also be a query or whatever. --->
<CFSET variables.To = "someone@somewhere.net">
<CFSET variables.Body = "This is some text (for) the body of the message.">

<!--- Need to escape any parenthesis using the backslash
(the parenthesis are used as control characters in FDF).
This step is only necessary if your values might contain '(' or ')' characters. --->
<CFSET variables.Body = ReplaceList( variables.Body, "(,)", "\(,\)" )>

<!--- If debug is not on, specify the file name and content type for the browser --->
<CFIF NOT URL.Debug>
<CFHEADER NAME="Content-Disposition" VALUE="inline; filename=myForm.fdf">
<CFCONTENT TYPE="application/vnd.fdf">
</CFIF>

<!--- Avoid any white space before the actual content is sent.
A leading blank line or space will cause an Acrobat error. --->
<CFSETTING ENABLECFOUTPUTONLY="NO"><CFINCLUDE TEMPLATE="myForm.fdf">
------ end code --------