Chapter 12: Existing Form Fill-in

Contents

12.1 Form Fill-in Overview

12.1.1 SetFieldValue Method

In Chapter 9 of this manual, we already described the process of filling in a form-like document (see Section 9.2).

The approach described in that chapter had one serious drawback: individual blanks in a "form" had to be referenced by their physical coordinates. This is because the Section 9.2 code sample did not involve a real form, but a regular (static) PDF document depicting a form.

Using tools such as Adobe Acrobat, it is possible to place interactive fields on an existing document using a graphical user interface. The form creator chooses a name, size, location and other options for each field. Once a form has been created, it can be filled in either interactively by a user, or automatically using tools such as AspPDF.NET. A form field can be referenced by name, as opposed to (x, y)-coordinates, which simplifies the coding significantly.

The PdfForm object provides the method FindField which returns a PdfAnnot object representing a top-level form field with the specified name. If no field under this name is found, the method returns Nothing.

PdfAnnot objField = objDoc.Form.FindField("txtLastName");

To specify a text value for a field, the method SetFieldValue of the PdfAnnot object should be used. This method accepts two arguments: a text string, and a font to be used to render the string. The SetFieldValue method uses the field's pre-defined alignment and font size parameters. It also preserves an existing field border if there is one.

As of Version 2.6, AspPDF.NET also supports the method SetFieldValueEx which only requires a single argument, the text string. This method is described below.

The SetFieldValue method can be used to set a value not only for text fields, but checkboxes, drop-down list boxes and radio buttons as well. To select or unselect a checkbox, you must specify a state name for the text string argument. The state name for the "on" state should be obtained via the FieldOnValue property. The state name for the "off" state is usually "Off".

When working with radio button groups, you need to use the PdfAnnot.Children collection to reference each individual radio button within a group. The code snippet below deselects radio button #1 (American Express) and selects radio button #2 via the SetFieldValue method.

The following code fragment fills out a credit card form generated in Section 11.2:

PdfAnnot objField = objDoc.Form.FindField("ccNumber");
objField.SetFieldValue( "324234324234", objDoc.fonts["Helvetica"] );

// Expiration month
PdfAnnot objField = objDoc.Form.FindField("ccMonth");
objField.SetFieldValue( "Mar", Doc.fonts["Helvetica"] );

// Expiration year
PdfAnnot objField = objDoc.Form.FindField("ccYear");
objField.SetFieldValue( "2010", Doc.fonts["Helvetica"] );

// "Need Receipt?" checkbox
PdfAnnot objField = objDoc.Form.FindField("Receipt");
objField.SetFieldValue( objField.FieldOnValue, null );// "on" state

// Credit card type (radio buttons)
PdfAnnot objField = objDoc.Form.FindField("ccType");
objField.Children[1].SetFieldValue( "Off", null ); // Unselect Amex
objField.Children[2].SetFieldValue( objField.Children[2].FieldOnValue, null ); // Select Visa

To make a field read-only, Bit 1 of the property FieldFlags needs to be set, as follows:

objField.FieldFlags |= 1;
Field.FieldFlags = Field.FieldFlags Or 1

12.1.2 SetFieldValueEx Method

As of Version 2.6, AspPDF.NET supports a more convenient way to fill in forms, via the PdfAnnot method SetFieldValueEx. This method takes a single argument, the text string. SetFieldValueEx internally calls SetFieldValue with a font object it automatically creates from the information contained in the PDF form being filled.

The SetFieldValueEx method simplifies the coding as the need for a font object is eliminated. For example, the code

PdfFont objFont = objDoc.Fonts["Helvetica"];
PdfAnnot objField = objDoc.Form.FindField("ccNumber");
objField.SetFieldValue( "324234324234", objFont );

can now be replaced with:

PdfAnnot objField = objDoc.Form.FindField("ccNumber");
objField.SetFieldValueEx( "324234324234" );

Note that the SetFieldValueEx method does not make SetFieldValue completely obsolete as the former relies on the fonts embedded in the PDF form, and those may not be sufficient to display all the necessary characters. In that case, an external font should be used via the method SetFieldValue.

12.2 Adobe Acrobat 7+/Designer 7+ Issues

The emergence of Adobe Acrobat 7.0 (and above) and Designed 7.0 (and above) brought about a number of significant changes to the PDF form specifications. Two changes that are most relevant to AspPDF.NET's form fill-in functionality are a new way of naming form fields, and the Adobe XML Forms Architecture (XFA).

When you switch to PDF forms created by Version 7 and later of the Adobe Acrobat suite, you may have to make some changes to your AspPDF.NET-based applications to accommodate the new Adobe specs.

12.2.1 Field Naming

Adobe Designer 7 assigns hierarchically-built names to form fields, such as form1[0].#subform[0].Address[0]. It is these long names that have to be passed to the Form.FindField method.

If you are not sure as to the full name of a field in your PDF, we recommend opening this document in a text editor (such as WordPad) and searching for your short field name (such as "Address"). You are likely to find a fragment which looks similar to this:

33 0 obj<</Rect[118.701 659.835 501.165 681.165]/TM(form1[0].#subform[0].Address[0]) /Subtype/Widget/TU(Address:)/Parent 29 0 R/F 4/P 8 0 R/Q 0/T<FEFF0041006400640072006500730073005B0030005D> /StructParent 2/DA(/TimesNewRomanPSMT 10.00 Tf 0 g)/FT/Tx/Type/Annot/MK<<>>>>
endobj

The string in parentheses followed by the word /TM is likely this field's long name which you can copy and paste into your script.

To help determine the names of the fields in your form, we have provided the Form Field Finder online application which enables you to upload your PDF document to our server, and the list of full field names in the document will be instantly displayed.

12.2.2 Adobe XML Forms Architecture (XFA)

In a nutshell, XFA is Adobe's new way to describe form structure and content using XML. XFA is only briefly mentioned in the official PDF 1.6 specifications, but the full description of this new standard is available from the Adobe.com web site.

An attempt to programmatically fill in an XFA-based form (such as a form created by Adobe Designer 7) using AspPDF.NET may fail as AspPDF.NET versions prior to 3.0 lacked XFA support. As a workaround, AspPDF.NET offers the method PdfForm.RemoveXFA which completely removes the XFA information from a form, thus making it compatible with older PDF form specifications, which AspPDF does support. In most cases, the user will not even notice any difference between an XFA-based and XFA-free form.

The following code snippet demonstrates the usage of the new method:

PdfDocument objDoc = objPDF.OpenDocument(@"c:\path\TheForm.pdf");
objDoc.Form.RemoveXFA();

PdfAnnot objField = objDoc.Form.FindField("form1[0].#subform[0].Name[0]");
objField.SetFieldValue( "John Smith", Doc.fonts["Times-Roman"] );
...
// Fill in other fields

UPDATE: XFA support was added to AspPDF.NET in Version 3.0. For details, see Section 12.7 - XFA Support of this chapter below.

12.3 Code Sample

The following code sample fills in a simple form created in Adobe Designer 7.0.

// create instance of the PDF manager
PdfManager objPDF = new PdfManager();

// Open existing form
PdfDocument objDoc = objPDF.OpenDocument( Server.MapPath( "SimpleForm.pdf" ) );

PdfFont objFont = objDoc.Fonts["Helvetica-Bold"]; // a standard font

// Remove XFA support from it
objDoc.Form.RemoveXFA();

// Fill in Name
PdfAnnot objField = objDoc.Form.FindField("form1[0].#subform[0].Name[0]");
objField.SetFieldValue( txtName.Text, objFont );

// Fill in Address
objField = objDoc.Form.FindField("form1[0].#subform[0].Address[0]");
objField.SetFieldValue( txtAddress.Text, objFont );

// Fill in marital status
int nIndex = 1;
if( rdMarried.Checked )
  nIndex = 2;

if( rdDivorced.Checked )
  nIndex = 3;

objField = objDoc.Form.FindField("form1[0].#subform[0].RadioButtonList[0]");
PdfAnnot objChildField = objField.Children[nIndex];
objChildField.SetFieldValue( objChildField.FieldOnValue, null );

// Fill in "How did you hear about us" checkboxes
if( chkInternet.Checked )
{
  objField = objDoc.Form.FindField("form1[0].#subform[0].Internet[0]");
  objField.SetFieldValue( objField.FieldOnValue, null );
}

if( chkWordOfMouth.Checked )
{
  objField = objDoc.Form.FindField("form1[0].#subform[0].WordOfMouth[0]");
  objField.SetFieldValue( objField.FieldOnValue, null );
}

if( chkNewspaper.Checked )
{
  objField = objDoc.Form.FindField("form1[0].#subform[0].Newspaper[0]");
  objField.SetFieldValue( objField.FieldOnValue, null );
}

string strFileName = objDoc.Save( Server.MapPath( "filledform.pdf"), false );
' create instance of the PDF manager
Dim objPDF As PdfManager = New PdfManager()

' Open existing form
Dim objDoc As PdfDocument = objPDF.OpenDocument( Server.MapPath( "SimpleForm.pdf" ) )

Dim objFont As PdfFont = objDoc.Fonts("Helvetica-Bold") ' a standard font

' Remove XFA support from it
objDoc.Form.RemoveXFA()

' Fill in Name
Dim objField As PdfAnnot = objDoc.Form.FindField("form1[0].#subform[0].Name[0]")
objField.SetFieldValue( txtName.Text, objFont )

' Fill in Address
objField = objDoc.Form.FindField("form1[0].#subform[0].Address[0]")
objField.SetFieldValue( txtAddress.Text, objFont )

' Fill in marital status
Dim nIndex As Integer = 1
If rdMarried.Checked Then nIndex = 2

If rdDivorced.Checked Then nIndex = 3

objField = objDoc.Form.FindField("form1[0].#subform[0].RadioButtonList[0]")
Dim objChildField As PdfAnnot = objField.Children(nIndex)
objChildField.SetFieldValue( objChildField.FieldOnValue, Nothing )

' Fill in "How did you hear about us" checkboxes
If chkInternet.Checked Then
  objField = objDoc.Form.FindField("form1[0].#subform[0].Internet[0]")
  objField.SetFieldValue( objField.FieldOnValue, Nothing )
End If

If chkWordOfMouth.Checked Then
  objField = objDoc.Form.FindField("form1[0].#subform[0].WordOfMouth[0]")
  objField.SetFieldValue( objField.FieldOnValue, Nothing )
End If

If chkNewspaper.Checked Then
  objField = objDoc.Form.FindField("form1[0].#subform[0].Newspaper[0]")
  objField.SetFieldValue( objField.FieldOnValue, Nothing )
End If

Dim strFileName As String = objDoc.Save( Server.MapPath( "filledform.pdf"), False )

Click the links below to run this code sample:

12.4 Image Field Handling

As of Version 2.4, AspPDF.NET is capable of filling in image fields via the method SetFieldImage of the PdfAnnot object. This method expects two arguments: an instance of the PdfImage object containing the image, and an optional parameter string or PdfParam object.

The parameters currently supported are Mode, ReadOnly and Alignment, all optional (the Alignment parameter was introduced in version 3.1.0.30169). Mode specifies the stretch mode of the image being rendered. The possible values are:

  • 1 - Scale to fit (default);
  • 2 - Stretch to fit;
  • 3 - Do not scale or stretch.

When Mode is set to 1 (Scale-to-fit mode), the Alignment parameter can also be used. The valid values are:

  • 0 - Alignment to the left or top (default);
  • 1 - Alignment to the right or bottom;
  • 2 - Alignment to the center.

By default, SetFieldImage makes the image field read-only. The ReadOnly parameter, if set to False, leaves the image field clickable.

SetFieldImage can only be called on image fields, all other field types will throw an exception. In most cases, calling RemoveXFA is also required for this method to work properly.

The following code sample demonstrates the use of this method:

PdfDocument objDoc = objPDF.OpenDocument(@"c:\path\form.pdf");
PdfImage objImage = objDoc.OpenImage(@"c:\path\image.jpg");
objDoc.Form.RemoveXFA();

PdfAnnot objField = objDoc.Form.FindField("form1[0].#subform[0].ImageField1[0]");
objField.SetFieldImage( objImage, "mode=2" );
Dim objDoc As PdfDocument = objPDF.OpenDocument("c:\path\form.pdf")
Dim objImage As PdfImage = objDoc.OpenImage("c:\path\image.jpg")
objDoc.Form.RemoveXFA()

Dim objField As PdfAnnot = objDoc.Form.FindField("form1[0].#subform[0].ImageField1[0]")
objField.SetFieldImage( objImage, "mode=2" )

12.5 Form Flattening

As of Version 2.6, AspPDF.NET offers form-flattening functionality via the PdfForm method Flatten. This method has no arguments.

To flatten a form means to turn all of its interactive fields into static graphics with no possibility for further editing. Once the Flatten method is called, the PDF document stops being a form and becomes a regular static template.

The Flatten method can only be called on an already filled-in form. Calling SetFieldValue/SetFieldValueEx on the same instance of the document has no effect on the output. Therefore, in order to fill in a form and then flatten it, the form has to be filled the regular way, saved, reopened and only then flattened, as follows:

PdfDocument objDoc = objPdf.OpenDocument( @"c:\path\form.pdf" );
PdfAnnot objField1 = objDoc.Form.FindField("Field1");
objField1.SetFieldValueEx( "Text1" );

PdfAnnot objField2 = objDoc.Form.FindField("Field2");
objField2.SetFieldValueEx( "Text2" );

...

PdfDocument objDoc2 = objPdf.OpenDocument( objDoc.SaveToMemory() );
objDoc2.Form.Flatten();
objDoc2.Save( @"c:\path\flattened.pdf", false );

After flattening, field text information may appear distorted on some documents. For example, character spacing may become incorrect. To avoid that, a "Save State" command should be put at the beginning of each page's existing content, and a matching "Restore State" command at the end of it, as follows:

foreach( PdfPage objPage in objDoc2.Pages)
{
   objPage.Background.SaveState();
   objPage.Canvas.RestoreState();
}
...
objDoc2.Form.Flatten();

UPDATE: As of Version 3.4.0.2, as explained in the following section, the code snippet above can be replaced by

objDoc2.Form.Modify("Flatten=true; Reset=true");

Form flattening is demonstrated by Live Demo #16 - Form Flattening.

12.6 JavaScript Removal and Other Features

As of Version 2.9, a new PdfForm method, Modify, has been introduced which combines and extends the XFA removal and form flattening functionality described above. The method also enables annotation flattening and JavaScript removal described below. More functions are expected to be added to this method in the future as AspPDF.NET's form-related functionality expands.

The Modify method expects a single argument: a PdfParam object or parameter string. The following Boolean parameters, all optional, are currently supported and can be used in any combination:

  • RemoveXFA - using this parameter is equivalent to calling the old PdfForm.RemoveXFA method.
  • Flatten - using this parameter is equivalent to calling the old PdfForm.Flatten method.
  • FlattenAnnots - this parameter is useful if the PDF form being flattened contains items that are not technically form fields but field-like annotations. The following line of code flattens both the fields and field-like annotations:
    objDoc.Form.Modify("Flatten=true; FlattenAnnots=true");
  • RemoveJavaScript - this parameter removes all JavaScript from the document's catalog. PDF forms created with Adobe products often contain JavaScript that displays the message "This PDF form requires a newer version of Adobe Acrobat" after the form has been flattened:

    The following line of code flattens the form and prevents this message from popping up by removing JavaScript from the document:

    objDoc.Form.Modify("Flatten=true; RemoveJavaScript=true");

  • Reset (introduced by Version 3.4.0.2) - this parameter attempts to reset the current graphics state of all pages of the document by placing the Save State ("q") command at the beginning of every page's content and the Restore State ("Q") command at the end of it. Resetting the graphics state may be needed if the flattening procedure produces unexpected output or no output at all.

The Modify method is demonstrated by Live Demo #16 - Form Flattening.

12.7 XFA Support

12.7.1 XFA Overview

A traditional PDF form contains annotation objects representing form fields. Adobe LifeCycle Designer 7.0+ introduced a new generation of forms called XFA, which stands for XML Forms Architecture. Under XFA, a form layout is defined by an XML document, called template, describing subforms, fields, static text, fonts, images and other elements of the form. Below is the screenshot of a very simple template containing a submit button, two text fields, and two mutually exclusive radio buttons. To save screen space, most of the XML nodes of that template are shown in a collapsed state:

The content of the form fields is also packaged as an XML document called dataset. Both the template and dataset (as well as other information in XML format) are embedded in a PDF document. A form viewer application such as Adobe Reader merges and template and dataset together using a well-defined set of rules and produces a filled-out form.

Even a blank XFA form usually contains a property structured XML-based dataset, but this XML document just contains empty values. For example, the initial dataset for the form shown above looks as follows:

If the dataset sheet is filled with values, such as

...
<TextField1>Text value 1</TextField1>
<TextField2>Text value 2</TextField2>
...

and plugged back into the PDF form, the fields will contain the specified values when the form is viewed.

One important advantage of the XFA architecture over traditional PDF forms is that is enables dynamically growing forms, while the traditional forms are inherently static.

The Adobe XFA format is extensive. Version 3.3 of the XFA specifications is a 1,585-page document. It is available on the Adobe web site and can be downloaded from here.

12.7.2 AspPDF.NET's XFA Support

AspPDF.NET's support for XFA forms is streamlined: it enables the template and dataset data to be retrieved from a PDF form, and it also enables the modified dataset data to be plugged back into the PDF form. AspPDF.NET provides no functionality for XML parsing and creation but fortunately both classic ASP and .NET provide ample built-in support for XML management.

AspPDF.NET's XFA functionality is encapsulated in the PdfForm object via the read-only property XFATemplate and read/write property XFADatasets (note the plural form of the word "datasets".) Also, there is an auxiliary property HasXFA which returns True if the PDF form contains XFA data. If this property returns False, the form contains no XFA information and the XFATemplate and XFADatasets properties cannot be used.

The online retrieval of the template and dataset of a PDF form is implemented by Live Demo #15.

To demonstrate AspPDF.NET's XFA functionality, a sample PDF purchase order form shipped with Adobe LiveCycle Designer, Purchase Order.pdf, will be used. It can be found under the \Samples\manual_11 subfolder of the installation.

This form contains all major types of fields: text boxes, radio boxes, drop-down lists, checkboxes, and a dynamically growing list of items. This form's dataset which can be retrieved using Live Demo #15 looks as follows:

Our code sample will fill out the following fields: PO Number (<txtPONum>), Ordered by Company (<txtOrderedByCompanyName>), a Terms and Conditions radio box(<TermsConditions>), the State Tax checkbox (<chkStateTax>) and State Tax Rate (<numStateTaxRate>). It will also add three purchase order items to the item list (<detail>).

The valid values for a radio button group (1 for cash and 2 for credit in our example) can be obtained from the form's template. The relevant snippet of the template (simplified for better readability) is shown below:

<exclGroup name="TermsConditions" x="4.7513mm" y="5.08mm">
  <field...>
    <caption>
      <value>
        <text>Cash</text>
      </value>
    </caption>
    <items>
      <integer>1</integer>
    </items>
  </field>
  <field...>
    <caption>
      <value>
        <text>Credit</text>
      </value>
    </caption>
    <items>
      <integer>2</integer>
    </items>
  </field>
</exclGroup>
XmlNode Node;

PdfManager objPdf = new PdfManager();

// Open an XFA form
PdfDocument objDoc = objPdf.OpenDocument(Server.MapPath("Purchase Order.pdf"));

// Check if the form contains XFA data
if (!objDoc.Form.HasXFA)
{
  lblResult.Text = "This form contains no XFA information.";
  return;
}

// Load XFA dataset from the PDF form to Microsoft XML processor object
XmlDocument Xml = new XmlDocument();
Xml.LoadXml(objDoc.Form.XFADatasets);

XmlNamespaceManager Mgr = new XmlNamespaceManager(Xml.NameTable);
Mgr.AddNamespace("xfa", "http://www.xfa.org/schema/xfa-data/1.0/");

// Fill PO number
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/header/txtPONum", Mgr);
Node.InnerText = "1234456577";

// Fill Ordered By Company
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/header/txtOrderedByCompanyName", Mgr);
Node.InnerText = "Acme, Inc.";

// Fill Terms and Conditions: 1 for cash, 2 for credit
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/total/TermsConditions", Mgr);
Node.InnerText = "2";

// Fill State Tax checkbox and state tax rate of 8.5%
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/total/chkStateTax", Mgr);
Node.InnerText = "1";
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/total/numStateTaxRate", Mgr);
Node.InnerText = "8.5";

// Add purchase order items

// First, delete two existing detail nodes under form1
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1", Mgr);
Node.RemoveChild(Xml.GetElementsByTagName("detail")[0]);
Node.RemoveChild(Xml.GetElementsByTagName("detail")[0]);

// Add three new detail nodes
for (int i = 1; i <= 3; i++)
{
  XmlNode Detail, Subdetail;

  Detail = Xml.CreateElement("detail");
  Subdetail = Xml.CreateElement("txtPartNum");
  Subdetail.InnerText = "PART#" + i.ToString();
  Detail.AppendChild(Subdetail);
  Subdetail = Xml.CreateElement("txtDescription");
  Subdetail.InnerText = "Description #" + i.ToString();
  Detail.AppendChild(Subdetail);
  Subdetail = Xml.CreateElement("numQty");
  Subdetail.InnerText = (5 * i).ToString();
  Detail.AppendChild(Subdetail);
  Subdetail = Xml.CreateElement("numUnitPrice");
  Subdetail.InnerText = (100 * i).ToString();
  Detail.AppendChild(Subdetail);

  Node.InsertBefore(Detail, Xml.GetElementsByTagName("total")[0]);
}

// Plug the dataset back to the PDF form
objDoc.Form.XFADatasets = Xml.InnerXml;

// Save document
string Path = Server.MapPath("xfaform.pdf");
string FileName = objDoc.Save(Path, false);
Dim Node As XmlNode

Dim objPdf As PdfManager = New PdfManager()

' Open an XFA form
Dim objDoc As PdfDocument = objPdf.OpenDocument(Server.MapPath("Purchase Order.pdf"))

' Check if the form contains XFA data
if Not objDoc.Form.HasXFA Then
  lblResult.Text = "This form contains no XFA information."
  Return
End If

' Load XFA dataset from the PDF form to Microsoft XML processor object
Dim Xml As XmlDocument= new XmlDocument()
Xml.LoadXml(objDoc.Form.XFADatasets)

Dim Mgr As XmlNamespaceManager = New XmlNamespaceManager(Xml.NameTable)
Mgr.AddNamespace("xfa", "http://www.xfa.org/schema/xfa-data/1.0/")

' Fill PO number
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/header/txtPONum", Mgr)
Node.InnerText = "1234456577"

' Fill Ordered By Company
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/header/txtOrderedByCompanyName", Mgr)
Node.InnerText = "Acme, Inc."

' Fill Terms and Conditions: 1 for cash, 2 for credit
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/total/TermsConditions", Mgr)
Node.InnerText = "2"

' Fill State Tax checkbox and state tax rate of 8.5%
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/total/chkStateTax", Mgr)
Node.InnerText = "1"
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1/total/numStateTaxRate", Mgr)
Node.InnerText = "8.5"

' Add purchase order items

' First, delete two existing detail nodes under form1
Node = Xml.DocumentElement.SelectSingleNode("/xfa:datasets/xfa:data/form1", Mgr)
Node.RemoveChild(Xml.GetElementsByTagName("detail")(0))
Node.RemoveChild(Xml.GetElementsByTagName("detail")(0))

' Add three new detail nodes
For i As Integer = 1 to 3
  Dim Detail As XmlNode, Subdetail As XmlNode

  Detail = Xml.CreateElement("detail")
  Subdetail = Xml.CreateElement("txtPartNum")
  Subdetail.InnerText = "PART#" + i.ToString()
  Detail.AppendChild(Subdetail)
  Subdetail = Xml.CreateElement("txtDescription")
  Subdetail.InnerText = "Description #" + i.ToString()
  Detail.AppendChild(Subdetail)
  Subdetail = Xml.CreateElement("numQty")
  Subdetail.InnerText = (5 * i).ToString()
  Detail.AppendChild(Subdetail)
  Subdetail = Xml.CreateElement("numUnitPrice")
  Subdetail.InnerText = (100 * i).ToString()
  Detail.AppendChild(Subdetail)

  Node.InsertBefore(Detail, Xml.GetElementsByTagName("total")(0))
Next

' Plug the dataset back to the PDF form
objDoc.Form.XFADatasets = Xml.InnerXml

' Save document
Dim Path As String = Server.MapPath("xfaform.pdf")
Dim FileName As String = objDoc.Save(Path, false)

Click the links below to run this code sample:

The resultant PDF form looks as follows:

12.8 Barcode-equipped Government Forms

Some U.S. government agencies, U.S. Citizenship and Immigration Services (USCIS) in particular, have started employing barcode-equipped forms. These forms have a large two-dimensional PDF417 barcode on each page representing the content of this page's fields. Every time a field is modified by the user, the barcode is automatically redrawn to reflect the change. The barcode enables the processing agent to transfer the content of a paper form to the computer system instantly.

The official electronic forms I-821, N-400, G-28, I-90, I-131 and I-864 are hybrid: they are based on both the traditional AcroForm and new XFA formats. The easiest way to fill them out programmatically with AspPDF.NET is to circumvent XFA altogether and use the FindField/SetFieldValueEx methods to fill out the individual fields, but doing so breaks the barcode functionality of the form. Therefore, the PDF417 barcode has to be drawn separately according to the government specifications. Afterwards, the form can optionally be flattened. AspPDF.NET's support for PDF417 barcodes is described in detail in Subsection 13.2.2 - PDF417 of the next chapter.

The USCIS requirements for the 2D barcode can be found here: http://www.uscis.gov/forms/uscis-2d-barcode-requirements.

The following code sample fills out Page 1 of USCIS Form N-400 ("Application for Naturalization"). This form can be downloaded from the USCIS.gov web site.

With the help of the Form Field Finder application mentioned above, Form N-400 is found to have the following fields on Page 1:

form1[0].#subform[0].Part1_Eligibility[0]
form1[0].#subform[0].Part1_Eligibility[1]
form1[0].#subform[0].Part1_Eligibility[2]
form1[0].#subform[0].Part1_Eligibility[3]
form1[0].#subform[0].Part1_Eligibility[4]
form1[0].#subform[0].Part1Line5_OtherExplain[0]
form1[0].#subform[0].Line1_MiddleName[0]
form1[0].#subform[0].Line1_GivenName[0]
form1[0].#subform[0].Line1_FamilyName[0]
form1[0].#subform[0].Line2_FamilyName[0]
form1[0].#subform[0].Line2_GivenName[0]
form1[0].#subform[0].Line3_MiddleName2[0]
form1[0].#subform[0].Line3_GivenName2[0]
form1[0].#subform[0].Line3_FamilyName2[0]
form1[0].#subform[0].Line3_MiddleName1[0]
form1[0].#subform[0].Line3_GivenName1[0]
form1[0].#subform[0].Line3_FamilyName1[0]
form1[0].#subform[0].Line2_MiddleName[0]
form1[0].#subform[0].#area[0].Line1_AlienNumber[0]
form1[0].#subform[0].PaperFormsBarcode1[0]

For the sake of demonstration, our code sample will fill out 9 form fields as follows:

Field Title
Field Name
Field Value
A-Number
form1[0].#subform[0].#area[0].Line1_AlienNumber[0]
123456789
Eligibility, "other" checkbox
form1[0].#subform[0].Part1_Eligibility[0]
<checked>
Eligibility, "explain" dropbox: RELIGIOUS DUTIES
form1[0].#subform[0].Part1Line5_OtherExplain[0]
SECTION 317,INA
Your current legal name, Last Name
form1[0].#subform[0].Line1_FamilyName[0]
Smith
Your current legal name, First Name
form1[0].#subform[0].Line1_GivenName[0]
John
Your current legal name, Middle Name
form1[0].#subform[0].Line1_MiddleName[0]
Frederick
Your name as it appears on resident card, Last Name
form1[0].#subform[0].Line2_FamilyName[0]
Smith
Your name as it appears on resident card, First Name
form1[0].#subform[0].Line2_GivenName[0]
John
Your name as it appears on resident card, Middle Name
form1[0].#subform[0].Line2_MiddleName[0]
Frederick

The rest of the fields will be left blank.

According to the government barcode specifications (link above), the text string encoded by the barcode for Page 1 must contain 18 values separated by the "|" character. The first three are form type ("N-400"), form revision ("09/13/13"), and page number ("1").

The other 15 values are the actual content of the form fields: A-Number ("123456789"), Eligibility - option 5 ("D"), Explanation - religious duties ("SECTION 317,INA"), Current legal name ("Smith", "John", "Frederick"), Name as it appears on resident card ("Smith", "John", "Frederick"), other names ("", "", "", "", "", "" ).

Therefore, the entire string to be encoded into a barcode is as follows:

"N-400|09/13/13|1|123456789|D|SECTION 317,INA|Smith|John|Frederick|Smith|John|Frederick|||||||"

To draw the barcode according to the specifications, the method PdfCanvas.DrawBarcode2D should be called with the following parameters (AspPDF 3.2.0.1+ required):

  • X=36, Y=36 (lower-left corner coordinates, obtained empirically);
  • Width=540, Height=108 (barcode size: 7.5 x 1.5 inches or 540 x 108 user units per specs);
  • QZV=6, QZH=8 (vertical and horizontal extents of the quiet zone around the barcode, obtained empirically);
  • ErrorLevel=5 (error correction level 5 per specs);
  • Columns=30 (to roughly match the specified bar width, values of 28 and 29 are also acceptable);
  • Rows=5 (to match the appearance of the barcode on the official form);
  • Binary=true (optional, see below.)

It is worth noting that the barcode on the official N-400 form does not strictly adhere to the government specifications. In particular, the barcode uses the text compression of data (which is more compact) while the specifications call for byte compression. The DrawBarcode2D uses text compression by default. If byte compression is required, the parameter Binary=true should be used. The code sample below does not use it, however.

// Form field values
string [] arrValues = new string[18]; // 18 values

arrValues[ 0] = "N-400"; // form type
arrValues[ 1] = "09/13/13"; // form revision
arrValues[ 2] = "1"; // page number
arrValues[ 3] = "123456789"; // A-Nunber
arrValues[ 4] = "D"; // Eligibility
arrValues[ 5] = "SECTION 317,INA"; // Explanation
arrValues[ 6] = "Smith"; // Legal name
arrValues[ 7] = "John";
arrValues[ 8] = "Frederick";
arrValues[ 9] = "Smith"; // Name as it appears on card
arrValues[10] = "John";
arrValues[11] = "Frederick";
arrValues[12] = ""; // Other names
arrValues[13] = "";
arrValues[14] = "";
arrValues[15] = "";
arrValues[16] = "";
arrValues[17] = "";

PdfManager objPdf = new PdfManager();
PdfDocument objDoc = objPdf.OpenDocument( Server.MapPath("n-400.pdf") );

// Disconnect XFA and JavaScript
objDoc.Form.Modify( "RemoveXFA=true; RemoveJavaScript=true" );

// Fill out fields
PdfAnnot objField = objDoc.Form.FindField("form1[0].#subform[0].#area[0].Line1_AlienNumber[0]");
objField.SetFieldValueEx( arrValues[3] );

// Option 5 is selected
objField = objDoc.Form.FindField("form1[0].#subform[0].Part1_Eligibility[0]");
objField.SetFieldValueEx( objField.FieldOnValue );

objField = objDoc.Form.FindField("form1[0].#subform[0].Part1Line5_OtherExplain[0]");
objField.SetFieldValueEx( "RELIGIOUS DUTIES" );

objField = objDoc.Form.FindField("form1[0].#subform[0].Line1_FamilyName[0]");
objField.SetFieldValueEx( arrValues[6] );

objField = objDoc.Form.FindField("form1[0].#subform[0].Line1_GivenName[0]");
objField.SetFieldValueEx( arrValues[7] );

objField = objDoc.Form.FindField("form1[0].#subform[0].Line1_MiddleName[0]");
objField.SetFieldValueEx( arrValues[8] );

objField = objDoc.Form.FindField("form1[0].#subform[0].Line2_FamilyName[0]");
objField.SetFieldValueEx( arrValues[9] );

objField = objDoc.Form.FindField("form1[0].#subform[0].Line2_GivenName[0]");
objField.SetFieldValueEx( arrValues[10] );

objField = objDoc.Form.FindField("form1[0].#subform[0].Line2_MiddleName[0]");
objField.SetFieldValueEx( arrValues[11] );

// Draw PDF417 barcode
string strBarcodeValue = ""; // |-terminated array of values to encode as a barcode
for( int i = 0; i < 18; i++ )
{
  strBarcodeValue = strBarcodeValue + arrValues[i] + "|";
}

PdfPage objPage = objDoc.Pages[1];
objPage.Canvas.DrawBarcode2D( strBarcodeValue, "X=36; Y=36; Width=540; Height=108; QZV=6, QZH=8; " +
"ErrorLevel=5; Columns=28; Rows=5" );

// Flatten form
PdfDocument objFlatDoc = objPdf.OpenDocument( objDoc.SaveToMemory() );
objFlatDoc.Form.Modify("Flatten=true; FlattenAnnots=true");

string strFilename = objFlatDoc.Save( Server.MapPath("formbarcode.pdf"), false );
' Form field values
Dim arrValues(17) As String ' 18 values

arrValues( 0) = "N-400" ' form type
arrValues( 1) = "09/13/13" ' form revision
arrValues( 2) = "1" ' page number
arrValues( 3) = "123456789" ' A-Nunber
arrValues( 4) = "D" ' Eligibility
arrValues( 5) = "SECTION 317,INA" ' Explanation
arrValues( 6) = "Smith" ' Legal name
arrValues( 7) = "John"
arrValues( 8) = "Frederick"
arrValues( 9) = "Smith" ' Name as it appears on card
arrValues(10) = "John"
arrValues(11) = "Frederick"
arrValues(12) = "" ' Other names
arrValues(13) = ""
arrValues(14) = ""
arrValues(15) = ""
arrValues(16) = ""
arrValues(17) = ""

Dim objPdf As PdfManager = New PdfManager()
Dim objDoc As PdfDocument = objPdf.OpenDocument( Server.MapPath("n-400.pdf") )

' Disconnect XFA and JavaScript
objDoc.Form.Modify( "RemoveXFA=true; RemoveJavaScript=true" )

' Fill out fields
Dim objField As PdfAnnot = objDoc.Form.FindField("form1[0].#subform[0].#area[0].Line1_AlienNumber[0]")
objField.SetFieldValueEx( arrValues(3) )

' Option 5 is selected
objField = objDoc.Form.FindField("form1[0].#subform[0].Part1_Eligibility[0]")
objField.SetFieldValueEx( objField.FieldOnValue )

objField = objDoc.Form.FindField("form1[0].#subform[0].Part1Line5_OtherExplain[0]")
objField.SetFieldValueEx( "RELIGIOUS DUTIES" )

objField = objDoc.Form.FindField("form1[0].#subform[0].Line1_FamilyName[0]")
objField.SetFieldValueEx( arrValues(6) )

objField = objDoc.Form.FindField("form1[0].#subform[0].Line1_GivenName[0]")
objField.SetFieldValueEx( arrValues(7) )

objField = objDoc.Form.FindField("form1[0].#subform[0].Line1_MiddleName[0]")
objField.SetFieldValueEx( arrValues(8) )

objField = objDoc.Form.FindField("form1[0].#subform[0].Line2_FamilyName[0]")
objField.SetFieldValueEx( arrValues(9) )

objField = objDoc.Form.FindField("form1[0].#subform[0].Line2_GivenName[0]")
objField.SetFieldValueEx( arrValues(10) )

objField = objDoc.Form.FindField("form1[0].#subform[0].Line2_MiddleName[0]")
objField.SetFieldValueEx( arrValues(11) )

' Draw PDF417 barcode
Dim strBarcodeValue As String = "" ' |-terminated array of values to encode as a barcode
For i As Integer = 0 to 17
  strBarcodeValue = strBarcodeValue + arrValues(i) + "|"
Next

Dim objPage As PdfPage = objDoc.Pages(1)
objPage.Canvas.DrawBarcode2D( strBarcodeValue, "X=36; Y=36; Width=540; Height=108; QZV=6, QZH=8; "+ _
"ErrorLevel=5; Columns=28; Rows=5" )

' Flatten form
Dim objFlatDoc As PdfDocument = objPdf.OpenDocument( objDoc.SaveToMemory() )
objFlatDoc.Form.Modify("Flatten=true; FlattenAnnots=true")

Dim strFilename As String = objFlatDoc.Save( Server.MapPath("formbarcode.pdf"), False )

NOTE: This code sample requires that the USCIS form N-400 (file n-400.pdf) be placed in the subfolder \Samples\manual_11 of the installation. This file was not included in the installation because of its large size.

Click the links below to run this code sample: