Question

I am developing a web app using Ruby on Rails 3. One of the features of the app is to use data from a MySQL database to fill PDF template forms that have been designed in Adobe LiveCycle Designer.

I am using the technique of generating an XFDF file with the data and use it to fill in the actual PDF file. I am using PDFtk to do this and if I run it from my command prompt (Windows 7 64bit) it works fine.

I used code by Greg Lappen at http://bleep.lapcominc.com/2012/02/07/filling-pdf-forms-with-ruby-and-pdftk/ to implement this process in my Rails app, but it does not seem to work

The output PDF cannot be opened in Acrobat as it states the file has been damaged. If I open it using a normal text editor all it contains is #<StringIO:0x5958f30> with the HEX value changing after each output.

The code generating the XML data is correct. I was able to save it to a file and run it through the command prompt myself.

def self.generate_xfdf(fields, filename)
    xml = Builder::XmlMarkup.new
    xml.instruct!
    xml.xfdf("xmlns" => "http://ns.adobe.com/xfdf/", "xml:space" => "preserve") {
      xml.f :href => filename
      xml.fields {
        fields.each do |field, value|
          xml.field(:name => field) {
            if value.is_a? Array
              value.each {|item| xml.value(item.to_s) }
            else
              xml.value(value.to_s)
            end
          }
        end
      }
    }
    xml.target!
  end

I suspect the real problem is in either of the two code snippets below. I just started learning Ruby on Rails and I am unable to debug this. I have tried various different methods but no success so far. I would really appreciate any help.

  def self.stamp(input_pdf, fields)
    stdin, stdout, stderr = Open3.popen3("pdftk #{input_pdf} fill_form - output - flatten")
    stdin << generate_xfdf(fields, File.basename(input_pdf))     
    stdin.close
    yield stdout
    stdout.close
    stderr.close
  end

PdfStamper.stamp('C:/clean-it-template.pdf', { 'LastName' => "Test Last Name", 'FirstName' => "Test First Name" }) do |pdf_io|
    pdf_content = StringIO.new
    pdf_content << pdf_io.read
    send_data pdf_content.string, :filename=>'output.pdf', :disposition=>'inline', :type=>'application/pdf'
end

This is the full code in my controller class

require 'pdf_stamper'

class FormPagesController < ApplicationController
    def pdftest
        PdfStamper.stamp('C:/clean-it-template.pdf', { 'LastName' => "Test Last Name", 'FirstName' => "Test First Name" }) do |pdf_io|
            pdf_content = StringIO.new
            pdf_content << pdf_io.read
            send_data pdf_content.string, :filename=>'output.pdf', :disposition=>'inline', :type=>'application/pdf'
        end
    end
end

This is the full code for the pdf_stamper class I am using

require 'builder'
require 'open3'

class PdfStamper
  def self.stamp(input_pdf, fields)
    stdin, stdout, stderr = Open3.popen3("pdftk #{input_pdf} fill_form - output - flatten")
    stdin << generate_xfdf(fields, File.basename(input_pdf))     
    stdin.close
    yield stdout
    stdout.close
    stderr.close
  end

  def self.generate_xfdf(fields, filename)

    xml = Builder::XmlMarkup.new
    xml.instruct!
    xml.xfdf("xmlns" => "http://ns.adobe.com/xfdf/", "xml:space" => "preserve") {
      xml.f :href => filename
      xml.fields {
        fields.each do |field, value|
          xml.field(:name => field) {
            if value.is_a? Array
              value.each {|item| xml.value(item.to_s) }
            else
              xml.value(value.to_s)
            end
          }
        end
      }
    }
    xml.target!
    #file = File.new("C:/debug.xml", "w+")
    #file.write(xml_data)
    #file.close
  end

end

UPDATE #1:

I ran the web app on Ubuntu and I still get the same errors. After digging around on the web I changed the code in my controller to this:

def pdftest
    PdfStamper.stamp('/home/nikolaos/clean-it-template.pdf', { 'LastName' => "Test Last Name", 'FirstName' => "Test First Name" }) do |pdf_io|
        pdf_content = StringIO.new("", 'wb')
        pdf_content << pdf_io.read
        send_data pdf_content.string, :filename=>'output.pdf', :disposition=>'inline', :type=>'application/pdf'
    end
end

I changed StringIO to be in binary write mode and it works in Ubuntu! The PDF opens correctly with all the fields filled in. I opened the same file on Windows using Acrobat and no problems, BUT if I run the web app on Windows, it still produces damaged PDF files.

Does anyone have any solutions on how to get this working in Windows? I am guessing it has something to do with the way Windows and Linux interpret newlines or something similar to that?

Était-ce utile?

La solution

After some more searching through the Ruby documentation I managed to solve my problem. Now my app is able to produce valid PDF files on Windows. Here is my solution for anyone that is experiencing the same problem.

The solution was to use IO instead of StringIO in the controller.

My FormPages controller code

require 'pdf_stamper'

class FormPagesController < ApplicationController
    def pdftest
        PdfStamper.stamp('C:/clean-it-template.pdf', { 'LastName' => "Bukas", 'FirstName' => "Nikolaos" }) do |pdf_io|
            pdf_content = IO.new(pdf_io.to_i, "r+b")
            pdf_content.binmode
            send_data pdf_content.read, :filename=>'output.pdf', :disposition=>'inline', :type=>'application/pdf'
        end
    end
end

The pdf_stamper class in charge of filling and generating the PDF using PDFtk

require 'builder'
require 'open3'

class PdfStamper
  def self.stamp(input_pdf, fields)
    Open3.popen3("pdftk #{input_pdf} fill_form - output -") do |stdin,  stdout, stderr|
      stdin << generate_xfdf(fields, File.basename(input_pdf))
      stdin.close
      yield stdout
      stdout.close
      stderr.close
    end
  end

  def self.generate_xfdf(fields, filename)

    xml = Builder::XmlMarkup.new
    xml.instruct!
    xml.xfdf("xmlns" => "http://ns.adobe.com/xfdf/", "xml:space" => "preserve") {
      xml.f :href => filename
      xml.fields {
        fields.each do |field, value|
          xml.field(:name => field) {
            if value.is_a? Array
              value.each {|item| xml.value(item.to_s) }
            else
              xml.value(value.to_s)
            end
          }
        end
      }
    }
    xml.target!
  end
end
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top