kuujinbo_dot_info

Yes, Full Text Search!

Posted 2009-08.

In Part 1 we retrieved a PDF template's form field coordinates. Let's put them to use and create simple multi-page billing statement.

Implement PdfPageEvent

First we're going to create a simple helper class by implementing PdfPageEvent. The purpose of the helper class is to:
  1. Read the templates so they are only used one time each. This reduces overhead and file size.
  2. Add the stored template page everytime a new page is created; in a real world scenario you'll probably be getting the customer's transactions from a database and won't know in advance how many pages the billing statement will require.
public class event_helper : PdfPageEventHelper {
  public const string PDF_TEMPLATE = "/cs/itext/bill_template.pdf";
  public const string PDF_TEMPLATE2 = "/cs/itext/bill_template2.pdf";
  public PdfImportedPage template;
  public int start_page_count = 1;
  // -------------------------------------------------------------------------
  // read/save PDF template page **one time** 
  public override void OnOpenDocument(PdfWriter writer, Document doc) {
    PdfReader reader = get_reader(PDF_TEMPLATE);
    template = writer.GetImportedPage(reader, 1);
    reader.Close();
  }
  // ----------------------------------------------------------------------------
  public override void OnEndPage(PdfWriter writer, Document document) {
    // second page on uses different template
    if (start_page_count == 2) {
      PdfReader reader = get_reader(PDF_TEMPLATE2);
      template = writer.GetImportedPage(reader, 1);
      reader.Close();
    }
    writer.DirectContentUnder.AddTemplate(template, 0, 0);
    ++start_page_count;      
  }
  // -------------------------------------------------------------------------
  public PdfReader get_reader(string pdf_template) {
    return new PdfReader(new RandomAccessFileOrArray(
        HttpContext.Current.Server.MapPath(pdf_template)
      ), null
    );
  }
}

Send Results to Client

iText makes this extremely easy for us; like the book says it's a five-step process:
  1. Create a Document object.
  2. Get a PdfWriter so we can use the templates.
  3. Open the Document.
  4. Add content; the code for _do_form_fields(), _get_transaction_details(), and _transaction_summary() are omitted, since they only return strings to add to ColumnText. ColumnText is smart; each call to Go() renders as much text that will fit on the current page and returns a status code that tells you: (1) how much text (to write) is remaining, and/or (2) how much space is still available on the page. On each iteration you add text to the current page, call ColumnText.HasMoreText() to inspect the status, and then Document.NewPage() if necessary.
  5. Close the Document.
// [1]
var document = new Document(PageSize.LETTER);
try {
  // [2] second parameter specific to ASP.NET
  var writer = PdfWriter.GetInstance(document, resp.OutputStream);
  writer.PageEvent = new event_helper();
  // [3]
  document.Open();
  // [4]
  var column_text = new ColumnText(writer.DirectContent);
  _do_form_fields(column_text, fill_font);
  _init_column_text(column_text, account_activity1);
  column_text.AddText(new Phrase(
    _get_transaction_details().ToString(), 
    fill_font
  ));
  column_text.AddText(_transaction_summary());
  do {
    column_text.Go();
    _init_column_text(column_text, account_activity2);
    document.NewPage();
  } while ( ColumnText.HasMoreText(column_text.Go()) );
}
catch { throw; }
// [5]
finally { if (document != null) document.Close(); }
_init_column_text() does need more explanation. ColumnText needs some way to calculate how much space it has to work with. Do this before you start adding text - that's where the coordinates for the templates in Part 1 come in:
// [1] combined transaction details
public readonly float[] account_activity1 = {62.9f, 48.7f, 549.3f, 550.3f};
public readonly float[] account_activity2 = {62.9f, 50f, 549.3f, 708f};
// [2] stand-alone values
public readonly float[] address_to = {149.3f, 620.1f, 398.7f, 696.1f};
public readonly float[] date_due = {311.4f, 725.5f, 425.5f, 738.9f};
public readonly float[] due_balance = {443.6f, 724.8f, 554.9f, 738.9f};
they're passed to SetSimpleColumn() to specify the exact available rectangular area:
/* lower left x-coordinate
 * lower left y-coordinate
 * upper right x-coordinate
 * upper right y-coordinate
*/
private void _init_column_text(ColumnText ct, float[] coord) {
  ct.SetSimpleColumn(
    coord[0], coord[1], coord[2], coord[3],
    12.0F, Element.ALIGN_LEFT
  );
}
We use an overloaded signature of SetSimpleColumn(), passing a Phrase as the first parameter, to set the "stand-alone" ([2] above) values. See the column_text.AddText() call above if you don't know how to create a Phrase. On to the demo.
(Number of Transactions)
Customer Address