Skip to main content

Overview

The system supports two types of signatures for inspection forms:
  1. Handwritten Signatures: Captured via signature pad and stamped on PDF forms
  2. Digital Certificates: Cryptographic signatures using X.509 certificates for legal compliance
Both signature types are applied to PDF forms using HexaPDF, ensuring compatibility with Adobe Acrobat and other PDF readers.

Signature Field Types

The system distinguishes between two signature field categories:

Technician Signatures (Signature_Field)

Used for inspector/technician certification:
{
  "name": "Inspector_Signature",
  "type": "Signature_Field",
  "section_name": "Certification",
  "label_name": "Inspector Signature",
  "is_signature": true
}
These fields are embedded in the main PDF form and can include digital certificates.

Client Signatures (Signature_Annex)

Used for customer acknowledgment:
{
  "name": "Customer_Signature",
  "type": "Signature_Annex",
  "section_name": "Customer Acknowledgment",
  "label_name": "Customer Signature",
  "is_signature": true
}
These signatures are rendered on dedicated annex pages with inspection report headers.

Signature Detection

The PdfSignatureService automatically detects signature fields when parsing PDF templates:
def self.list_signature_fields(file_path)
  doc = HexaPDF::Document.open(file_path)
  return [] unless doc.acro_form
  
  fields = []
  doc.acro_form.each_field do |field|
    next unless signature_field?(field)
    
    info = extract_signature_info(field)
    fields << {
      name: extract_field_name(field),
      is_signed: !info.nil?,
      info: info
    }
  end
  fields
end

Signature Field Detection Logic

Fields are identified as signatures if:
def self.signature_field?(field)
  (field.respond_to?(:field_type) && field.field_type == :Sig) ||
    field.type == :Sig
end
This detects PDF signature annotations with /FT /Sig field type.

Handwritten Signatures

Technicians and customers can provide handwritten signatures using a signature pad interface.
1

Capture Signature

During form fill, click on a signature field to open the signature pad
2

Draw Signature

Use mouse, trackpad, or touchscreen to draw signature
3

Save as PNG

Signature is saved as a PNG image with transparent background
4

Attach to Form Fill

Image is attached via Active Storage with unique identifier:
def generate_unique_signature_attachment_id(field_name, field_section, field_type)
  signature_type = case field_type&.to_s
    when "Signature_Field" then "technician"
    when "Signature_Annex" then "client"
    else "technician"
  end
  
  "inspection_#{inspection.id}_signature_#{signature_type}_#{SecureRandom.hex(4)}.png"
end

Signature Image Storage

Signatures are stored with specific naming conventions to distinguish types:
  • Technician: inspection_123_signature_technician_a3f2.png
  • Client: inspection_123_signature_client_b8c1.png
This ensures the correct signature is applied to each field type.

Stamping Signature Images

Handwritten signatures are stamped onto PDF signature fields using the stamp_signature_image method:
def self.stamp_signature_image(file_path, output_path, field_name, image_path, 
                                scale_to_fit: true, margin: 0, allow_upscale: false)
  doc = HexaPDF::Document.open(file_path)
  field = doc.acro_form.field_by_name(field_name)
  
  # Get signature field widget (visible area)
  widget = field.each_widget.first
  rect = widget[:Rect]
  
  # Calculate image placement
  llx, lly, urx, ury = rect.value
  width = (urx - llx).abs
  height = (ury - lly).abs
  inner_width = [width - 2 * margin, 0].max
  inner_height = [height - 2 * margin, 0].max
  
  # Load and scale signature image
  image = doc.images.add(image_path)
  scale = [inner_width / image.width.to_f, inner_height / image.height.to_f].min
  scale = [scale, 1.0].min unless allow_upscale
  
  draw_w = image.width * scale
  draw_h = image.height * scale
  
  # Center image in signature field
  x = llx + margin + (inner_width - draw_w) / 2.0
  y = lly + margin + (inner_height - draw_h) / 2.0
  
  # Draw on overlay canvas
  page = find_page_for_widget(doc, widget)
  canvas = page.canvas(type: :overlay)
  canvas.image(image, at: [x, y], width: draw_w, height: draw_h)
  
  doc.write(output_path, optimize: true)
end
Signature images are scaled to fit within the field boundaries while maintaining aspect ratio. By default, images are not upscaled to prevent pixelation.

Digital Certificates

For legally binding signatures, the system supports digital signing with X.509 certificates.

Supported Certificate Formats

FormatExtensionDescription
PKCS#12.p12, .pfxCombined certificate and private key with password protection
PEM.pem, .crtSeparate certificate and key files

Applying Digital Signatures

Digital signatures are applied during PDF generation:
def self.sign(file_path, output_path, field_name, 
              certificate_path:, certificate_password: nil, key_path: nil,
              reason: nil, location: nil, contact_info: nil, name: nil,
              signature_image_path: nil)
  
  doc = HexaPDF::Document.open(file_path)
  signer = build_signer(certificate_path, certificate_password, key_path)
  
  appearance = if signature_image_path && File.exist?(signature_image_path)
    {
      type: :image,
      image: signature_image_path
    }
  else
    {
      type: :text,
      text: build_appearance_text(name: name, reason: reason, location: location)
    }
  end
  
  doc.sign(
    output_path,
    signer: signer,
    signature_field: field_name,
    reason: reason,
    location: location,
    contact_info: contact_info,
    name: name,
    sub_filter: 'adbe.pkcs7.detached', # PAdES-compatible
    appearance: appearance
  )
end

PAdES Compliance

The system uses the adbe.pkcs7.detached subfilter, which is compatible with:
  • PDF Advanced Electronic Signatures (PAdES)
  • Adobe Acrobat signature validation
  • European eIDAS regulations
  • Long-term signature validation (LTV)
PDF Advanced Electronic Signatures (PAdES) is a set of restrictions and extensions to PDF signatures that ensures:
  • Authenticity: Verifies the signer’s identity
  • Integrity: Detects any modifications after signing
  • Non-repudiation: Signer cannot deny having signed
  • Long-term validation: Signatures remain valid even after certificate expiry
The system implements PAdES-B-B (baseline) level, suitable for most legal and compliance requirements.

Certificate Configuration

To use digital signatures, configure certificate details in the signing request:
PdfSignatureService.sign(
  input_pdf,
  output_pdf,
  'technician_signature',
  certificate_path: '/path/to/cert.p12',
  certificate_password: 'secret123',
  reason: 'Fire Safety Inspection Certification',
  location: 'San Francisco, CA',
  contact_info: '[email protected]',
  name: 'John Doe',
  signature_image_path: '/path/to/handwritten_sig.png'
)

Signature Metadata

When a PDF is digitally signed, the following metadata is embedded:
SignatureInfo = Struct.new(
  :name,              # Signer name
  :signing_time,      # When the signature was applied
  :reason,            # Reason for signing
  :location,          # Geographic location
  :contact_info,      # Email or phone
  :sub_filter,        # Signature encoding format
  keyword_init: true
)
This metadata can be extracted from signed PDFs:
def self.signature_info(file_path, field_name)
  doc = HexaPDF::Document.open(file_path)
  field = doc.acro_form.field_by_name(field_name)
  
  v = field.dict[:V]
  return nil unless v.is_a?(HexaPDF::Dictionary)
  
  SignatureInfo.new(
    name: v[:Name],
    signing_time: v[:M],
    reason: v[:Reason],
    location: v[:Location],
    contact_info: v[:ContactInfo],
    sub_filter: v[:SubFilter]
  )
end

Signature Workflow

The complete signature workflow during PDF generation:

Implementation in PdfFormsParserService

def fill_form(output_path, field_data)
  normal_fields, signature_requests = partition_signature_requests(field_data)
  
  # Fill normal fields first
  @pdftk.fill_form(@file_path, output_path, prepare_field_values(normal_fields))
  
  intermediate_path = output_path
  
  # Apply signatures one by one
  signature_requests.each do |sig|
    tmp_out = Tempfile.create(['signed_', '.pdf'])
    field_name = sig['original_name'].presence || sig['name']
    
    if sig['certificate_path'].present?
      # Digital signature with certificate
      PdfSignatureService.sign(
        intermediate_path,
        tmp_out.path,
        field_name,
        certificate_path: sig['certificate_path'],
        certificate_password: sig['certificate_password'],
        signature_image_path: sig['signature_image_path']
      )
    else
      # Handwritten signature only
      PdfSignatureService.stamp_signature_image(
        intermediate_path,
        tmp_out.path,
        field_name,
        sig['signature_image_path']
      )
    end
    
    intermediate_path = tmp_out.path
  end
  
  FileUtils.cp(intermediate_path, output_path)
end

Client Signature Annex Pages

Customer signatures are rendered on dedicated annex pages with inspection report headers:
def self.add_signature_annexes(pdf_object, signature_images, inspection)
  contractor = ContractorInfo.first
  license = LicenseInfo.first
  
  annex_pdf_data = Prawn::Document.new(page_size: "LETTER") do |pdf|
    signature_images.each do |image|
      # Header section with inspection details
      pdf.text "Report of Inspection / Test", size: 16, style: :bold
      pdf.text "Date: #{inspection.date}"
      pdf.text "Property: #{inspection.property.address}"
      pdf.text "Conducted by: #{contractor.name}"
      
      # Signature section
      pdf.text "Customer's Signature", style: :bold
      
      # Signature table with customer name, signature image, and date
      # ... signature rendering code ...
    end
  end.render
  
  pdf_object << CombinePDF.parse(annex_pdf_data)
end
Client signature pages include the company logo, contractor information, license number, and inspection metadata for a professional appearance.

Signature Validation

To verify a signed PDF’s authenticity:
1

Open in Adobe Acrobat

Adobe Acrobat Reader shows a blue ribbon for valid signatures
2

Check Signature Panel

View signature details including:
  • Signer name and certificate
  • Signing time
  • Document integrity status
  • Certificate trust chain
3

Verify Certificate

Ensure the certificate is issued by a trusted Certificate Authority (CA)
Self-signed certificates will show as “validity unknown” in Adobe Acrobat. For legal compliance, use certificates from trusted CAs like DigiCert, GlobalSign, or government-issued certificates.

Best Practices

Separate Signature Types

Use Signature_Field for technician signatures and Signature_Annex for customer acknowledgments to maintain clear separation

Use High-Quality Images

Save signature pad captures as PNG with transparent backgrounds at 300 DPI for professional appearance

Secure Certificate Storage

Store P12/PFX files in encrypted storage with restricted access. Never commit certificates to version control.

Include Metadata

Always provide reason, location, and contact info when digitally signing for audit trail completeness

Form Management

Configure signature fields in form templates

Inspections

Complete inspections and capture signatures on-site

PDF Parsing

How signature fields are detected from PDF templates

Photo Management

Manage visual documentation alongside signatures

Build docs developers (and LLMs) love