Ruby on Rails
HowtoGeneratePDFs

This howto covers several approaches to generating a PDF document with Rails.

Using HTMLDOC

The sample code below requires HTMLDOC.

(note: I banged my head against Broken Pipe errors until I discovered that if you are using Apache, HTMLDOC runs as a CGI process and bombs out unless you add the htmldoc_env declaration and inclusion into the process call)

  #in controller
  def pdf
    @article = Article.find(@params["id"])
    add_variables_to_assigns
    htmldoc_env = "HTMLDOC_NOCGI=TRUE;export HTMLDOC_NOCGI" 
    generator = IO.popen("#{htmldoc_env};htmldoc -t pdf --path \".;http://#{@request.env["HTTP_HOST"]}\" --webpage -", "w+")
    generator.puts @template.render("article/pdf")
    generator.close_write

    send_data(generator.read, :filename => "#{@article.title}.pdf", :type => "application/pdf")
  end

If you’re using Windows, you may have problems unless you add the following after generator = IO.popen….

generator.binmode

Comments:
If your language require a different character set (Portuguese, Spanish, etc.), try this workaround:


generator = IO.popen("iconv -f UTF-8 -t iso-8859-1 | htmldoc --charset iso-8859-1 -t pdf --path \".;http://#{@request.env["HTTP_HOST"]}/\" --webpage  -", "w+")

- Leonardo Eloy

PDF::HTMLDOC

Alternatively, PDF::HTMLDoc is a gem wrapping HTMLDOC in a easy-to-use class for Ruby and Rails. It can be downloaded from RubyForge or using the usual gem commands.

You’ll need to have htmldoc installed.

PdfWriter

Alternatively if you want more control over where everything is written/drawn onto the page James Hollingshead has put some code up at http://www.hollo.org/pdfwriter . It also generates everything in a single pass so no need for temporary files. It is very lightweight and all in a single file that can be copied to the lib directory and required. Using it is as simple as:

  #in controller
  def pdf
    send_data gen_pdf, :filename => "something.pdf", :type => "application/pdf" 
  end

 private
  def gen_pdf
      pdf = PdfWriter.new
      pdf.newPage
          pdf.writeText(10, 200, 'Text to write', :fontsize => 18)
          pdf.writeLine(0, 0, 100, 100) #Draw line
      pdf.newPage
          pdf.writeText(10, 210, 'Now on page 2')
      pdf.writeEnd
  end

PDF::Writer (Austin Ziegler)

http://rubyforge.org/projects/ruby-pdf/

(Instructions updated by Austin Ziegler.)

Install PDF::Writer (and dependencies) with \RubyGems:

gem install pdf-writer

warning: requiring pdf-writer causes ActiveRecords transaction stuff to explode. see here and here

(on 2007-02-01 with transaction-simple-1.3.0 and activerecord-1.15.1 – there are warnings about redefined constants, but things seem to work. Further testing in progress.)

Also, there is a known memory leak with tables, see here

One way is to create a .pdf file in public/pdf and send it to the browser with a redirect, as shown below:

  #in controller
  require 'pdf/writer'

  def pdf
    gen_pdf
    redirect_to("#{@request.relative_url_root}/pdf/hello.pdf")
  end

  private
  def gen_pdf
    pdf = PDF::Writer.new
    pdf.select_font "Times-Roman" 
    pdf.text "Hello, Ruby.", :font_size => 72, :justification => :center

    pdf.save_as("public/pdf/hello.pdf")
  end

Alternately, generate the document and send it directly to the browser:

 # in controller
 require 'pdf/writer'

 def pdf
   _p = PDF::Writer.new
   _p.select_font 'Times-Roman'

   _p.text "Hello, Ruby.", :font_size => 72, :justification => :center
   send_data _p.render, :filename => 'filename', :type => "application/pdf" 
 end

This is the preferred way to send documents, as the documents will be sent inline and two requests won’t step on each others’ generated documents. There will be further details on what is possible in an upcoming Ruby Code & Style article that I’m writing.

Another alternative method is to create a template handler to handle, say, rpdf files with :


require 'pdf_render'
ActionView::Base.register_template_handler 'rpdf', ActionView::PDFRender

in your config/environment.rb file, and put the following somewhere in the lib directory as pdf_render.rb:


require 'pdf/writer'
module ActionView # :nodoc:
  class PDFRender
    PAPER = 'A4'
    include ApplicationHelper

    def initialize(action_view)

      @action_view = action_view
    end

    # Render the PDF
    def render(template, local_assigns = {})
      @action_view.controller.headers["Content-Type"] ||= 'application/pdf'

      #support to pdf on SSL for IE6
      @action_view.controller.headers["Cache-Control"] = 'maxage=3600' 
      @action_view.controller.headers["Pragma"] = 'public' 
      #end of support to pdf on SSL for IE6

      # Retrieve controller variables
      @action_view.controller.instance_variables.each do |v|
        instance_variable_set(v, @action_view.controller.instance_variable_get(v))
      end

      pdf = ::PDF::Writer.new( :paper => PAPER )
      pdf.compressed = true if RAILS_ENV != 'development'
      eval template, nil, "#{@action_view.base_path}/#{@action_view.first_render}.#{@action_view.pick_template_extension(@action_view.first_render)}" 

      pdf.render
    end
  end
end

And in your app/views/foo/bar.rpdf file you put


pdf.select_font "Times-Roman" 
pdf.text "Hello, Ruby.", :font_size => 72, :justification => :center

If you want to use ActionView helpers via this method, just use the @action_view instance variable:


pdf.text "Price is:" 
pdf.text @action_view.number_to_currency(500)

Check you’re not using a layout for actions rendering an rpdf template

Note: if you’re on a Mac and you get ‘JPEG marker not found’ or ‘undefined method `unpack’ for nil:\NilClass (\NoMethodError)’ errors with the above, this seems to be a problem with Apple’s version of Ruby on Tiger. See this thread: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/145411

Installing Ruby with \DarwinPorts is one possible solution.

If you’re having issues with the pdf views source code being rendered and you use Globalize plugin, this will do the trick: http://hervalicio.us/blog/2008/05/20/globalize-versus-pdf-writer-round-one/

If you would like to have the user prompted to download the file instead of displaying it within the window (can be useful for handling validation prior to download), then add the following to your PDFRender class:


@action_view.controller.headers["Content-Disposition"] ||= 'attachment'

However, watch out when setting the “global properties” of the controller using the @action_view instance variable. If you are using render_to_string to render a PDF as an attachment for an email, for instance, you will need to remove these calls from the PDFRender class otherwise they will fool your controller into thinking you want to output a using the PDF mime type to the web user.

UTF8
To print UTF8 correctly create a helper like this:


require 'iconv'

def replace_UTF8(field)
  ic_ignore = Iconv.new('ISO-8859-15//IGNORE//TRANSLIT', 'UTF-8')
  field = ic_ignore.iconv(field)
  ic_ignore.close

  field
end

In your controller use this:


person.lastname = replace_UTF8(person.lastname)

__

I found a small bug inside Pdf Writer gem that causes troubles when you try to hide the acrobat toolbar in the viewer and i created a patch to fix it

Ruby FPDF

An other alternative is Ruby FPDF, a port of PHP FPDF. It’s just one small Ruby file, which can be dropped in your Rails application “lib” folder. Download at http://brian.imxcc.com/fpdf/ (moved to http://zeropluszero.com/software/fpdf/ ?). Many examples, plus a font generator, are included.

  #in controller
  def pdf
    send_data gen_pdf, :filename => "something.pdf", :type => "application/pdf" 
  end

 private
  def gen_pdf
    pdf=FPDF.new
    pdf.AddPage
    pdf.SetFont('Arial','B',16)
    pdf.Cell(40,10,'Hello World!')
    pdf.Output 
  end

Here is an example of using content stored in a database and generating a PDF with FPDF.
Here is a problem that occurs, when trying to include JPGs or PNGs into the PDF on Mac OS: ErrorUsingFPDFWithJPGOrPNGOnMacOS

Fpdf::Table allows easy adding tables to Ruby FPDF.

JasperReports

JasperReports is a powerful—and even more important—well known open source Java reporting tool that has the ability to deliver rich content in formats such as PDF, RTF, HTML, CSV and XML. Read HowtoIntegrateJasperReports into Rails.

Notes

Headers for Internet Explorer

Note that you may have to play around a bit to get send_data to work with Internet Explorer. The following lines worked wonders for me (see the API docs for more info on send_data):


  if @request.env['HTTP_USER_AGENT'] =~ /msie/i
    @headers['Pragma'] = ''
    @headers['Cache-Control'] = ''
  else
    @headers['Pragma'] = 'no-cache'
    @headers['Cache-Control'] = 'no-cache, must-revalidate'
  end

Do not use a layout

If you are not using send_data, make sure you disable layout for your pdf method. Note: This can also be accomplished by render_without_layout

class YourController < ApplicationController
  layout "layouts/yourLayout" ,  :except => :yourPdfMethod

  def yourPdfMethod
     ..
  end
end

PDF Form Fill

Using all the tools listed above to create a nice looking pdf file will take you a lot of time to learn how to do. The easier way is to create a form using Adobe Acrobat – or, if you prefer “free”, OpenOffice’s PDF export (http://www.openoffice.org) can do this too. Simply use the text field tool to create where dynamic text should be entered in at and give them variable names. Now use this script to create an FDF compatible file…


def createFDF(info)
    data = "%FDF-1.2\x0d%\xe2\xe3\xcf\xd3\x0d\x0a"; # header
    data += "1 0 obj\x0d<< " # open the Root dictionary
    data += "\x0d/FDF << " # open the FDF dictionary
    data += "/Fields [ " # open the form Fields array

      info.each { |key,value|
          if value.class == Hash
              value.each { |sub_key,sub_value|
                  data += '<< /T (' + key + '_' + sub_key + ') /V '
                  data += '(' + sub_value.to_s.strip + ') /ClrF 2 /ClrFf 1 >> '
              }
          else
              data += '<< /T (' + key + ') /V (' + value.to_s.strip + ') /ClrF 2 /ClrFf 1 >> '
          end
      }

    data += "] \x0d" # close the Fields array
    data += ">> \x0d" # close the FDF dictionary
    data += ">> \x0dendobj\x0d" # close the Root dictionary

    # trailer note the "1 0 R" reference to "1 0 obj" above
    data += "trailer\x0d<<\x0d/Root 1 0 R \x0d\x0d>>\x0d" 
    data += "%%EOF\x0d\x0a" 
    afile = File.new("/tmp/rails_" + rand.to_s, "w") << data
    afile.close
    return afile
  end

This function will return your fdf temp file, Now to enter that info into a pdf you will need pdftk found at http://www.accesspdf.com/pdftk/

There is a bug here: if the fields have chars that need to be escaped, such as parenthesis. Anyone know all the acceptable chars for an FDF?

A couple Notes of my experience with formfill
1) The form must be created in a version of Acrobat prior to 7.0, they use a different method of creating a form, using xml and the method above won’t work
2) Regarding the parenthesis issue. You need to escape them by using the ”\” Character then you won’t have any problems.

Once that is installed you can do something like this…

u = User.find(:first)

fdf = createFDF(u.attributes)

pdf_output = `pdftk ./user_info.pdf fill_form #{fdf.path} output - flatten`
File.delete(fdf.path)

Next just pass the pdf_output to the browser for the user to get the pdf file, or save it in the database.

Is it possible to insert images with this methods?—Helder

Happy hacking! – Chief

Note: I had issues using the backtick method above. The pdf info that `pdftk…` was returning was in binary and my Windows machine didn’t like that and treated is a regular string. I used the following method to open the output of that command as a binary stream and read the data:

u = User.find(:first)

fdf = createFDF(u.attributes)

pdf_output = nil

IO.popen("pdftk some_file.pdf fill_form #{fdf.path} output - flatten ","rb") { |io|
      pdf_output = io.read      
} 

Now you can do stuff with pdf_output.

NOTE:
In addition to the pdftk there is a Java library called iText using the Ruby Java Bridge you can access the functionality of iText. This post provides more details and some code samples on how to accomplish filling in an existing PDF with data via the acroformfields.

PDFlib and PDFlib-Lite

PDFlib newest release contains Ruby bindings. PDFlib and PDFlib-Lite is one of the fastest PDF generation libraries in production. This is a commercial library though (unless you meet their strict requirements for their opensource license).

For installation and usage information, you can view this 2 part series by Bob Silva

Generating PDFs in Rails – Part I – Installing
Generating PDFs in Rails – Part II - Real World Usage

Libharu

libharu newest release contains Ruby bindings for Windows. Libharu is also extremely fast, and is completely free for any use.

Comments:
Disclaimer: Personal Opinion. One of the things that is holding Rails away from the enterprise is its reporting solutions (or lack of). There’s no tool in the neatness of JasperReports (yet). – Tamer Salama

Comments:
Regarding PDF FORM FILL - Where would you put the script to create the FDF file? In the controller?

Rfpdf Plugin

I am a long time user of PDFlib. When I started working with Ruby on Rails, like Ruby on Rails I searched for a free PDF capable solution. I tried RTex with mixed results – sometimes it worked sometimes it didn’t. Then I found Ruby on FPDF. I have been very pleased.

I did like the template view capability of RTex, which accommodated embedding the ruby code in files with .rtex extensions.

I also had a client that needed Chinese, Japanese and Korean support. These languages were supported in the PHP version of FPDF but only Chinese had been ported and that port didn’t work properly so I spent the weekend porting these three languages to Ruby from PHP.

The Rfpdf Plugin incorporates: Ruby FPDF, e-ruby template view support (.rfpdf files) and additional Asian support for Chinese, Japanese and Korean languages.

Download it at http://rubyforge.org/projects/rfpdf/ or see
the other install/example details at Rfpdf Plugin.

pdftk

Ruby/GnomePrint and Ruby/Pango

There are various ways to generate PDFs with combinations of the various Ruby bindings to the gnome libraries, as mentioned in the following ruy-talk thread:

http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-talk/184721?184657-187655+split-mode-vertical:http://blade.nagaokaut.ac.jp/cgi-bin/vframe.rb/ruby/ruby-talk/184721?184657-187655+split-mode-vertical.

These libraries are all unicode aware, as shown in the following example files:

arabic sample
m17n sample

PDF::Wrapper is a project that wraps these libraries in a API designed with PDF document generation in mind. It’s still in alpha stages, but is worth a look.

JRuby and iText

Deman of CoderSifu.blogspot.com explain how to produce PDF using JRuby and iText at http://codersifu.blogspot.com/2007/06/tutorial-on-producing-pdf-file-using.html

To create and fill PDF forms with predefined textfields in JRuby check this out http://virtualsingularity.com/2008/05/filling-pdf-forms-with-jruby-and-itext/

Inkscape

“how, given your database, a PDF form, Ruby on Rails, plus a few other things, you can produce filled-out PDF documents. This assumes Debian/Ubuntu.”

http://www.thesatya.com/blog/2007/09/inkscaperailspdf.html

category: Howto

Free Screencast : Extremely Simple PDF Generation in Rails using HTMLDOC