Adding an unsigned signature to a PDF using Seam and iText

Seam provides a nice component library that uses iText to generate a PDF document. In the process of building a proposal management system, I found myself needing to add an unsigned signature to a PDF. The requirement was to provide an external user with the ability to approve a proposal.

For the sake of those that have never created a custom facelet component, I have provided the details of how this is done throughout this post.

At first I thought I could use the existing p:signature tag in the Seam/PDF component library. This tag is not documented so I wasn’t sure how to use it or what it did. After looking at the source, I realized this is really an “author” signature. Basically, its a way for the document receiver to validate the source of the document and for them to know that it has not been altered.

So, I ended up creating a custom tag to handle adding an unsigned signature.

First, I created a taglib.xml file and modified the build to place it under “WEB-INF/classes/META-INF/facelets”.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE facelet-taglib PUBLIC
  "-//Sun Microsystems, Inc.//DTD Facelet Taglib 1.0//EN"
  "http://java.sun.com/dtd/facelet-taglib_1_0.dtd">
<facelet-taglib xmlns="http://java.sun.com/JSF/Facelet">
    <namespace>http://www.kdmooreconsulting.com.com/facelets/pdf</namespace>
    <tag>
        <tag-name>unsignedSignature</tag-name>
        <component>
           <component-type>com.kdmc.components.ui.pdf.UIUnsignedSignature</component-type>
        </component>
    </tag>  
</facelet-taglib>

Secondly, I added the component to faces-config.xml.

<?xml version='1.0' encoding='UTF-8'?>
<faces-config version="1.2"
              xmlns="http://java.sun.com/xml/ns/javaee"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-facesconfig_1_2.xsd">

   <application>
      <locale-config>
         <default-locale>en</default-locale>
         <supported-locale>en</supported-locale>
      </locale-config>
      <view-handler>com.sun.facelets.FaceletViewHandler</view-handler>
   </application>
  
   <component>
       <component-type>com.kdmc.components.ui.pdf.UIUnsignedSignature</component-type>
       <component-class>com.kdmc.components.ui.pdf.UIUnsignedSignature</component-class>
   </component>

</faces-config>

Thirdly, I created the unsigned signature component with the following signature.

pdf:unsignedSignature Description
Renders an unsigned signature. If size is not provided, it is assumed the parent tag will be a p:cell.
Attributes
  • field – the name of the field. This will show up in the pdf signature panel. Always Required.
  • size – a String containing rectangle parameters in the form of “llx lly urx ury”. For example, size=”72, 132, 144, 780″. Required if parent tag is not a p:cell

package com.kdmc.components.ui.pdf;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import org.jboss.seam.pdf.ITextUtils;
import org.jboss.seam.pdf.ui.ITextComponent;
import org.jboss.seam.pdf.ui.UICell;
import org.jboss.seam.pdf.ui.UIDocument;

import com.lowagie.text.DocWriter;
import com.lowagie.text.Rectangle;
import com.lowagie.text.pdf.PdfAcroForm;
import com.lowagie.text.pdf.PdfAnnotation;
import com.lowagie.text.pdf.PdfContentByte;
import com.lowagie.text.pdf.PdfFormField;
import com.lowagie.text.pdf.PdfPCell;
import com.lowagie.text.pdf.PdfPCellEvent;
import com.lowagie.text.pdf.PdfWriter;

public class UIUnsignedSignature extends ITextComponent
{
    String field;
    String size;

    public void setField(String field)
    {
       this.field = field;
    }
    
    public void setSize(String size)
    {
       this.size = size;
    }
    
    @Override
    public void encodeEnd(FacesContext context) throws IOException
    {
        PdfPCell cell = getCell();
        float[] rect = new float[]{0,0,0,0};
        
        // If the unsigned signature is not inside a table cell then the size must be provided.
        if (cell == null)
        {
     	   rect = determineSize(context);
        }
        
        PdfWriter writer = findWriter();
        if (writer == null)
        {
           throw new RuntimeException("Cannot find PdfWriter - the document may not exist or may not be a pdf type");
        }
       
        PdfAcroForm form = writer.getAcroForm();
        PdfFormField signatureField = form.addSignature(getField(context), rect[0], rect[1], rect[2], rect[3]);
        
        // Add cell event if being placed in a table cell
        if (cell != null)
        	cell.setCellEvent(new FieldCell(signatureField, writer));
    }
    
    protected PdfPCell getCell()
    {
    	PdfPCell cell = null;
        UIComponent parent = getParent();
        if (parent instanceof UICell)
        {
        	UICell uiCell = (UICell)parent;
        	Object iTextObject = uiCell.getITextObject();
        	if (iTextObject instanceof PdfPCell)
        		cell = (PdfPCell) iTextObject;
        }
        return cell;
    }
    
    protected float[] determineSize(FacesContext context)
    {
        size = (String) valueBinding(context, "size", size);
        if (size == null)
        {
           throw new RuntimeException("signature size is required if not being using in a table cell");
        }
        float[] rect = ITextUtils.stringToFloatArray(size);
        if (rect.length != 4)
        {
           throw new RuntimeException("size must contain four numbers");
        }
        
        return rect;
    }
    
    private PdfWriter findWriter()
    {
       UIDocument doc = (UIDocument) findITextParent(this, UIDocument.class);
       if (doc != null)
       {
          DocWriter writer = doc.getWriter();

          if (writer instanceof PdfWriter)
          {
             return (PdfWriter) writer;
          }
       }
       return null;
    }
    
    protected String getField(FacesContext context)
    {
    	field = (String) valueBinding(context, "field", field);
        if (field == null)
        {
           throw new RuntimeException("signature field is required");
        }
        return field;
    }
    
    /**
     * Inner class to handle putting signature inside a cell with the same width as the cell
     *
     */
    public class FieldCell implements PdfPCellEvent {
        PdfFormField formField;
        PdfWriter writer;
        int width;
       
        public FieldCell(PdfFormField formField, PdfWriter writer){
                this.formField = formField;
                this.writer = writer;
        }

        public void cellLayout(PdfPCell cell, Rectangle rect, PdfContentByte[] canvas){
            formField.setWidget(new Rectangle(rect.getLeft(),
                 rect.getBottom(),
                 rect.getLeft() + cell.getWidth(),
                 rect.getTop()),
                 PdfAnnotation.HIGHLIGHT_NONE);
            writer.addAnnotation(formField);
        }
    }
    
    @Override
    public void createITextObject(FacesContext context)
    {
    }

    @Override
    public void removeITextObject()
    {
    }

    @Override
    public Object getITextObject()
    {
    	return null;
    }

    @Override
    public void handleAdd(Object other)
    {
       throw new RuntimeException("Unsigned signature does not accept children");
    }
}

And finally, I created the pdf document xhtml.

<p:document xmlns:p="http://jboss.com/products/seam/pdf"
            xmlns:pdf="http://www.kdmooreconsulting.com/facelets/pdf"
            fileName="#{proposalinfoHome.instance.name} proposal.pdf" 
            disposition="attachment"
            title="#{proposalinfoHome.instance.name}" 
            subject="#{proposalinfoHome.instance.subject}" 
            keywords="proposal" 
            author="#{proposalinfoHome.instance.author}" 
            creator="#{proposalinfoHome.instance.creator}">

<!-- Other document content -->

    <p:paragraph>
        <p:table columns="2" widths="2 4" headerRows="1" widthPercentage="100">
            <p:cell backgroundColor="#BED6F8">Role</p:cell>
            <p:cell backgroundColor="#BED6F8">Signature</p:cell>
            <p:cell><p:text value="Operations VP" /></p:cell>
            <p:cell><pdf:unsignedSignature field="Operations VP" /></p:cell>
            <p:cell><p:text value="Sales VP" /></p:cell>
            <p:cell><pdf:unsignedSignature field="Sales VP" /></p:cell>
        </p:table>
    </p:paragraph>

</p:document>

The main drawback to this solution is that the user will need to take the generated PDF and use Adobe Acrobat Pro to “reader enable” the signing capability. A better approach would have been to generate a link on the PDF back into the application that would validate the approver and allow them to “approve” the proposal. However, for political reasons, we will not have access to the proprietary certificate servers; so we can’t validate the certs. And the client doesn’t want the external approver(s) to have to login to the app and approve.