Collecting Data Directly from PDF

In many cases collecting user data for an organization's official form is best done using a web form. However, PDF forms can do the same thing. And if it's doable in PDF, you can bet iText can help you out.

For this simplified example, we're going to specify the following business process:

  1. The organization's form is used for employee rating.
  2. A supervisor fills in the rating part.
  3. The employee fills in the acknowledgement part.
  4. When the rating is complete the form should not be editable. The data should be archived for future reference in FDF format.

Processing the Request

We're using a HTTP Handler to stream the PDF to the client, since it's has the lowest overhead. We're doing a simple check to see if data is being sent via POST. If so the data is updated. Obviously in a real-world application the FDF data will be stored in a database, nota cookie, and authorization will be implemented.

// this object gives us access to all the PDF form fields
private AcroFields _form;
// save FDF data as Base64 MIME encoded string so we can store in 
// a cookie for this example
private HttpCookie _cookie_info;
// cookie name
public const string FDF_COOKIE = "__FDF_COOKIE__";
private HttpContext _hc;
// -------------------------------------------------------------------------
public void ProcessRequest (HttpContext context) {
  _hc = context;
  _cookie_info = _hc.Request.Cookies.Get(FDF_COOKIE);
  if (_hc.Request.RequestType == "POST") {
    using (var s = _hc.Request.InputStream) {
      var l = (int) s.Length;
      var ba = new byte[l];
      s.Read(ba, 0, l);
      if (_cookie_info == null) {
        _cookie_info = new HttpCookie(FDF_COOKIE);
      }
      _cookie_info.Expires = DateTime.Now.AddMonths(1);
      _cookie_info.Values[FDF_COOKIE] = Convert.ToBase64String(ba);
      _hc.Response.Cookies.Add(_cookie_info);
      _hc.Response.Redirect("itext_button_fields.aspx#demo");
    }
  }
  _stream_pdf();
}

Streaming the Content

Nothing new here, we (1) get the PDF Form, (2) get the form fields, (3) fill any existing data, and (4) set the view, which is dependent upon who is viewing the form.

public void _stream_pdf() {
  HttpResponse rs = _hc.Response;
  rs.ContentType = "application/pdf";
  PdfStamper ps = null;
  try {
    // (1)
    var r = new PdfReader(
      new RandomAccessFileOrArray(_hc.Server.MapPath(PDF_TEMPLATE)), 
      null
    );
    ps = new PdfStamper(r, rs.OutputStream);
    // (2)
    _form = ps.AcroFields;
    // (3)
    if (_cookie_info != null) {
      var fdfr = new FdfReader(
        Convert.FromBase64String(_cookie_info.Values[FDF_COOKIE])
      );
      if (fdfr != null) {
        _form.SetFields(fdfr);
      }
    }
    // (4) see 'Dynamically Changing the Form' section below
    _set_user_editable(r);
  }
  catch { throw; }
  finally { if (ps != null) ps.Close(); }
}

Dynamically Changing the Form

We're doing a few things here:

  1. Replace a button on the original form and dynamically set it's properties when the rating is "active".
  2. Set read-only flag for input fields depending on who's viewing the form.
  3. Remove the button from the original form if the rating is complete. Obviously it would have been simpler to hard-code the form name in this simplified example, but the extra step was added to show how to remove a field from a PDF form.
public const string QS_EMPLOYEE = "employee";
// --------------------------------------------------------------------------
private void _set_user_editable(PdfReader r) {
  var done = _hc.Request.QueryString["done"] != null;
  var is_employee = _hc.Request.QueryString[QS_EMPLOYEE] != null;
  var keys = _form.Fields.Keys;
  // (1) see 'Replacing the Button' section below
  if (!done) _init_button(r);
  // save pushbutton(s); remove from form when rating completed
  var btn = new List<string>();
  foreach(string key in _form.Fields.Keys) {
    var t = _form.GetFieldType(key);
    if (t != AcroFields.FIELD_TYPE_PUSHBUTTON) {
      // show completed rating form, no more updates allowed 
      if (done) {
       // see 'Read-only Fields' section below
        _set_field_readonly(key);
      }
      // (2)
      else {
        var swe = key.StartsWith(QS_EMPLOYEE);
        // supervisor enters employee data
        if (!is_employee && swe) {
          _set_field_readonly(key);
        }
        // only employee can acknowledge rating
        else if (is_employee && !swe) {
          _set_field_readonly(key);
        }
      }
    }
    else {
      btn.Add(key);
    }
  }
  // (3)
  if (done) {
    foreach (var k in btn) {
      _form.RemoveField(k);
    }
  }
}

Replacing the Button

The inline comments explain everything...

public const string BUTTON_NAME = "submit";
// dynamically set properties of existing button that does nothing
private void _init_button(PdfReader r) {
  // existing button will be replaced with a new one
  PushbuttonField bt = _form.GetNewPushbuttonFromField(BUTTON_NAME);
  bt.Options = PushbuttonField.VISIBLE_BUT_DOES_NOT_PRINT;
  PdfFormField pff = bt.Field;
  // * set URL
  // * submit form data as FDF
  pff.Action = PdfAction.CreateSubmitForm(
    _hc.Request.RawUrl, null, PdfAction.SUBMIT_CANONICAL_FORMAT 
  );
  _form.ReplacePushbuttonField(BUTTON_NAME, pff);
}

Read-only Fields

There are a couple of ways to make a form read-only:

As explained on pages 506-507 in iText in Action, the second method is needed to retrieve the data for this example. The first method "stamps" the text on the document; text can no longer be retrieved from named form fields.

// make form data inputs read-only, depending on who is viewing form
private void _set_field_readonly(string key) {
  _form.SetFieldProperty(
    key, "setfflags", PdfFormField.FF_READ_ONLY, null
  );
}

Demo

Again, cookies must be enabled for this to work. After you save the data you'll be redirected back here.

  1. View as supervisor; fill in employee rating.
  2. View as employee; acknowledge only.
  3. View as "completed" rating; all fields read-only, submit button removed.
  4. Start over

More kuujinbo.info iText examples

iText References