Just listen to Alex

May 1, 2009

JRuby on Rails, PDF generation and Flying Saucers

Filed under: Uncategorized — bosmeeuw @ 2:01 pm

There are tons of ways to generate PDF documents in just about any language. If you’re using Ruby, you have a number of libraries at your disposal to generate PDF documents using the specific API’s of these libraries. But if you have a Rails or other Ruby web application, the primary goal of your application is outputting HTML to be displayed in clients web browsers. So, wouldn’t it be nice to generate your PDF reports the same way, by outputting HTML?

If you’re running JRuby on Rails, this is a piece of cake! Using the great Flying Saucer Project (AKA xhtmlrenderer), you can convert your HTML + CSS to PDF documents. The great thing about Flying Saucer is that it will mostly render your HTML to PDF the same way standard compliant browsers will render it to screen. You have most of CSS 2.1 at your disposal, plus some custom CSS properties to handle print-specific matters such as paginated tables, and complex headers and footers. It has suprisingly few bugs, which really is an achievement when you’re talking about a HTML renderer. Did I mention it’s pretty fast, even for very large documents?

Of course, this is a Java library, so you need to be running JRuby to use it from your Rails application (or you need to resort to interfacing with it through the command line). First thing you need to do is installing the Flying Saucer gem, provided by the Wolfe project:

jruby -S gem install flying_saucer

This will automatically download the needed java library jars (flying saucer itself and iText) and put them in your gems folder.

Next, create a Rails controller you will use to output your PDF documents, for instance “ReportingController”. Let’s have it output a basic Hello World PDF.

The code for the controller (note the “render_pdf” method):

require "java"
require "flying_saucer"

class ReportingController < ApplicationController
  layout "pdf"

  def hello
    @text = "Hello world"

    render_pdf("hello.pdf")
  end

  private

  def render_pdf(filename)
    @html = render_to_string

    html_file = java.io.File.createTempFile(params&#91;:action&#93;, ".html")
    html_file.delete_on_exit

    pdf_file = java.io.File.createTempFile(params&#91;:action&#93;, ".pdf")
    pdf_file.delete_on_exit

    file_output_stream = java.io.FileOutputStream.new(html_file)
    file_output_stream.write(java.lang.String.new(@html).get_bytes("UTF-8"))
    file_output_stream.close

    renderer = org.xhtmlrenderer.pdf.ITextRenderer.new

	# if you put custom fonts in the lib/fonts folder under your Rails project, they will be available to your PDF document
	# Just specify  the correct font-family via CSS and Flying Saucer will use the correct font.
    fonts_path = RAILS_ROOT + "/lib/fonts/*.ttf"

    if File.exist?(fonts_path)
      font_resolver = renderer.getFontResolver()

      Dir&#91;fonts_path&#93;.each do |file|
        font_resolver.add_font(file, true)
      end
    end

    renderer.set_document(html_file)
    renderer.layout
    renderer.createPDF(java.io.FileOutputStream.new(pdf_file), true)

    send_file pdf_file.path, :type => "application/pdf", :filename => filename, :disposition => "attachment"
  end
end

The main layout for our PDF documents:

<html>
   <head>
      <style type="text/css">
         body {
            font-family: sans-serif;
         }
      </style>
   </head>
   <body>
      <h1>My PDF Wielding Application Title</h1>

      <%= yield %>
   </body>
</html>

And the code to the view for the “hello” action:

Cliché message goes here: <strong><%=h @text %></strong>

Surfing to /reporting/hello will generate this PDF document.

So, the gist is: render all you want (as long as it’s properly escaped XHTML), call the render_pdf method and voila, instant PDF! Feel free to re-use your existing partials and helpers, it’s all good.

One remark: if you refer to resources like stylesheets and images, you must use an absolute URL or FlyingSaucer won’t be able to access them. For instance, use:

<link rel="stylesheet" type="text/css" href="http://app.example.com/stylesheets/reports.css" />

In stead of:

<link rel="stylesheet" type="text/css" href="/stylesheets/reports.css" />

Happy reporting!

Advertisements

8 Comments »

  1. That’s very cool

    Comment by Tom — May 2, 2009 @ 4:59 pm

  2. […] JRuby on Rails, PDF generation and Flying Saucers […]

    Pingback by The Rubyist: May 2009 Edition « Juixe TechKnow — June 1, 2009 @ 5:25 pm

  3. […] JRuby on Rails, PDF generation and Flying Saucers « Just listen to Alex Wouldnt it be nice to generate your PDF reports the same way, by outputting HTML? […]

    Pingback by links for 2009-10-22 « Boskabout — October 22, 2009 @ 7:05 pm

  4. Hi,

    i am using flying saucer in the same way as above to generate a pdf.

    But the content which is written on the html page which is not saved is not reflecting in the generated pdf.

    Can you give any suggestions on how to capture the data which is not saved in the view to be applied to the pdf.

    Thanks,
    Keshav.

    Comment by Keshav — April 16, 2011 @ 1:49 am

  5. Using java.file.io.outputsteam to send file through is giving problems when running app on glassfish 3.0.1 using jruby 1.5.6. What happens is that the send file command closes the stream as soon as the file is sent. The result is a 500 server error with rails telling you that the steam is closed as soon as you click on a form button. OK, lets say am at a form, and I type in the server url with reporting/hello. If I choose not to save the file, and I decide click on the button in my form, I get this message. Also use the render_pdf could also be a cause of the problem as you would normally be taken to another page or action. I think this would be very similar to trying to use render :back which does’nt take you anywhere. Somehow a method will need to be found of using a java stream without affecting the jruby stream. Judging from the error, it looks like if you work with the java stream, you pretty much are working with jruby server stream as well, which would cause unexpected problems.

    Unfortunately I cannot use webrick in production as I need a server that supports context roots like glassfish 3….. Hope a solution to this could be found……….

    Comment by petdeal — June 9, 2011 @ 2:41 pm

    • Think I might have found the solution. If you are trying to call the reporting controller from a view, make sure that if you have a form tag, it uses a get instead of post. This seems to solve the problem. However, flying saucers can only be used with jdk 1.6 update 17 or lower, due to java.lang.linkage errors in Glassfish 3 occuring on newer jdk versions from Oracle.

      Comment by petdeal — June 10, 2011 @ 7:08 am

  6. Hi,
    I’m using the flying saucer to generate a simple pdf and I’m having a strange behavior.
    The pdf and the html file gets generated correctly in my tmp folder but when it is send back to user as an attachment I’m receiving a blank pdf file.
    If I open the file in the tmp folder I can see the pdf correctly.

    One strange thing I’m seeing is that the pdf in the tmp folder is slightly smaller then the one download from the link.

    Has anyone seen anything like this? Or any clue of what can be happening?

    Thanks in advance,
    Helio

    Comment by Helio — October 17, 2012 @ 4:40 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: