Prince.rb update

Michael Day from YesLogic emailed me the other day to alert me to a possible problem with my original HTML/CSS to PDF in Rails code.

We’ve recently received a bug report on our forum of Ruby leaving zombie Prince processes running on UNIX systems:

http://princexml.com/bb/viewtopic.php?t=1149

It looks as if this issue affects Seth’s module and probably the Princely plugin which appears to be based upon it.

I believe the solution for this is to add a call to pdf.close_read as mentioned at the end of that thread.

Cheers,

Michael

— Print XML with Prince! http://www.princexml.com

Below I’ve pasted the newest code, and here’s a link to the fixed prince.rb library.

Enjoy.

Prince.rb

# Prince XML Ruby interface. 
# http://www.princexml.com
#
# Library by Subimage Interactive - http://www.subimage.com
#
#
# USAGE
# -----------------------------------------------------------------------------
#   prince = Prince.new()
#   html_string = render_to_string(:template => 'some_document')
#   send_data(
#     prince.pdf_from_string(html_string),
#     :filename => 'some_document.pdf'
#     :type => 'application/pdf'
#   )
#

class Prince

  attr_accessor :exe_path, :style_sheets, :log_file

  # Initialize method
  #
  def initialize()
    # Finds where the application lives, so we can call it.
    @exe_path = `which prince`.chomp
      @style_sheets = ''
      @log_file = "#{RAILS_ROOT}/log/prince.log"
  end

  # Sets stylesheets...
  # Can pass in multiple paths for css files.
  #
  def add_style_sheets(*sheets)
    for sheet in sheets do
      @style_sheets << " -s #{sheet} "
    end
  end

  # Returns fully formed executable path with any command line switches
  # we've set based on our variables.
  #
  def exe_path
    # Add any standard cmd line arguments we need to pass
    @exe_path << " --input=html --server --log=#{@log_file} "
    @exe_path << @style_sheets
    return @exe_path
  end

  # Makes a pdf from a passed in string.
  #
  # Returns PDF as a stream, so we can use send_data to shoot
  # it down the pipe using Rails.
  #
  def pdf_from_string(string)
    path = self.exe_path()
    # Don't spew errors to the standard out...and set up to take IO 
    # as input and output
    path << ' --silent - -o -'

    # Show the command used...
    #logger.info "\n\nPRINCE XML PDF COMMAND"
    #logger.info path
    #logger.info ''

    # Actually call the prince command, and pass the entire data stream back.
    pdf = IO.popen(path, "w+")
    pdf.puts(string)
    pdf.close_write
    output = pdf.gets(nil)
    pdf.close_read
    return output
  end
end
seth, Tue, 05 Feb 2008 08:56:00 GMT
no comments

Rails Rumble - Have you voted yet?

If you haven’t done so already, go check out and vote for your favorite Rails Rumble entry of 2007.

Some amazing applications have been created in the time span of only two days.

Of course, we’ll be giving away prizes to the winners, along with many other great sponsors.

seth, Tue, 18 Sep 2007 21:02:00 GMT
no comments

Cashboard is an official sponsor of Rails Rumble 2007

I’m proud to announce that Cashboard is an official sponsor of the Rails Rumble 2007.

The Rails Rumble is a competition to test your Ruby on Rails hacking skills, build some cool new web apps, and make some friends.

The concept is simple: you get 48 hours to design, develop, and deploy a web application from scratch. After those 48 hours are up, you’ll be judged by the community through a peer-ranking system in a variety of categories (see Rules for more information). After about a week of controlled mayhem, judging wraps up and the dust settles. Winners are then declared, and awarded some cool prizes. We’ll be announcing what those prizes are shortly.

We will be giving away 6 year-long Maple subscriptions (a $1080 value each) to Cashboard as prizes, one for each category winner.

seth, Fri, 10 Aug 2007 15:04:00 GMT
2 comments

HTML / CSS to PDF using Ruby on Rails

Ever tried to save a web page and send it to someone? What about printing a web page? Both pretty much suck.

A lot of people are talking about printing with HTML and CSS these days but what they don’t tell you is the following:

With CSS you can’t…
  • Determine where page breaks happen.
  • Set page size or type (landscape or portrait).
  • Print background colors…some browsers don’t even print images.
  • Set page footers.

Wow, that’s lame.

Ever tried sending someone a web page? Not a link, but the actual web page. It doesn’t work well at all.

A little history

I wanted to be able to print or send estimates and invoices from our Ruby on Rails application, Cashboard. A good majority of our users felt the same way.

Just sending a link to someone to view a HTML document online doesn’t cut it. What if you want to snail mail that document? Shouldn’t it look the same on the page as it does on your screen?

When I used to create invoices manually I’d always save PDF files for my clients and email them.

PDF handled printing well, was portable, and my clients couldn’t edit the file.

We needed PDF for Cashboard.

The Prince of PDFs

I spent a good week just researching all of the PDF libraries and programs out there.

The ones I found for Ruby wanted me to re-create my documents in some PDF-specific layout language. That wasn’t going to cut it. See, in Cashboard we already had these documents designed in HTML. On top of that, we allow users to customize the colors and upload their logo for invoices. Duplicating the layout wasn’t an option.

After exhausting those possibilities I started looking into HTML to PDF programs. I found a few, but none that were any good at converting HTML and CSS to PDF.

This all changed when I ran across Prince XML. Not only is Prince great at converting HTML and CSS to PDF, it even passed the Acid2 test. They had a ton of demos available on their web site, and provide their program free for evaluation purposes.

Making it happen

Prince is a command line program, available for whatever platform you’re probably running.

It can take a variety of inputs including files on disk, or even HTTP urls. We run a pretty tight ship on Cashboard, and everything is password protected.

Luckilly Prince also can take input from standard in, and can even pass its output back to standard out. This means if you don’t want to mess with saving files and dealing with cleaning them up you don’t have to.

prince.rb, pdf_helper.rb

I cooked up a very simple Ruby library to call Prince and a helper module to include on my Rails controllers. I store them both in the lib folder of my Rails application.

Here’s the full helper, slightly modified for simplicity:

# We use this chunk of controller code all over to generate PDF files.
#
# To stay DRY we placed it here instead of repeating it all over the place.
#
module PdfHelper
  require 'prince'

  private
    # Makes a pdf, returns it as data...
    def make_pdf(template_path, pdf_name, landscape=false)
      prince = Prince.new()
      # Sets style sheets on PDF renderer.
      prince.add_style_sheets(
        "#{RAILS_ROOT}/public/stylesheets/application.css",
        "#{RAILS_ROOT}/public/stylesheets/print.css",
        "#{RAILS_ROOT}/public/stylesheets/prince.css"
      )
      prince.add_style_sheets("#{RAILS_ROOT}/public/stylesheets/prince_landscape.css") if landscape
      # Render the estimate to a big html string.
      # Set RAILS_ASSET_ID to blank string or rails appends some time after
      # to prevent file caching, fucking up local - disk requests.
      ENV["RAILS_ASSET_ID"] = ''
      html_string = render_to_string(:template => template_path, :layout => 'document')
      # Make all paths relative, on disk paths...
      html_string.gsub!("src=\"", "src=\"#{RAILS_ROOT}/public")
      # Send the generated PDF file from our html string.
      return prince.pdf_from_string(html_string)
    end

    # Makes and sends a pdf to the browser
    #
    def make_and_send_pdf(template_path, pdf_name, landscape=false)
      send_data(
        make_pdf(template_path, pdf_name, landscape),
        :filename => pdf_name,
        :type => 'application/pdf'
      ) 
    end
end

This simple module has two methods. Both take an ERB template path as an argument, then a pdf file name.

Make_pdf renders the template to a string, then does some modifications to make all requests within local. I’m passing in all CSS files locally as well, so I don’t have to deal with authentication.

I’ve created some special CSS files for printing, and even can pass in if I’d like the page to be laid out in a portrait or landscape format.

When it’s done it returns the PDF file as data. Nothing is rendered to disk. This method is useful for not only sending the PDF file to the client (as in make_and_send_pdf), but when generating PDF files for email attachments.

Creating PDF files from the controller

Both of these files make creating a PDF file dead easy.

Here’s a slimmed down version of Cashboard’s estimate controller.

class Provider::EstimatesController < Provider::BaseController
  include PdfHelper
    # Sends pdf of an estimate out...
    #
    def pdf
      # @estimate is set with a before_filter and isn't relevant for this how-to ;)
      make_and_send_pdf('/client/estimates/show', @estimate.pdf_name)
    end
end

Does it get any easier than that? Check out an example of a PDF file generated with Prince, straight from Cashboard. (Right click and save to your disk…)

Download

Here again are the libraries I’ve created for using Prince XML with Ruby on Rails:

They’re free to use, download em and check em out.

seth, Tue, 29 May 2007 16:37:00 GMT
10 comments

Cashboard presentation at May's Silicon Valley Rails Meetup

I’ll be giving a presentation about Cashboard at the upcoming Silicon Valley Ruby on Rails meetup. If you want some of the behind-the-scenes insight on the product it’ll be worth attending.

For times and directions click here.

Hope to meet some of you there!

seth, Thu, 26 Apr 2007 18:14:00 GMT
no comments

Cashboard ALPHA goes live!

You heard right…..our first hosted application just launched!

If you’re managing estimates, invoices, payments, and projects any other way – you need this software.

Cashboard Invoice Screen

Check out the announcement or – get Cashboard now!

seth, Wed, 04 Apr 2007 18:44:00 GMT
no comments

Dynamic CSS On Rails

Staying up entirely too late working on a project tonight I was sent a link for this really interesting way to generate dynamic CSS files using Ruby on Rails.

If you ever longed to use variables for repetitive things like color or background images you will love this too.

Most likely this will be powering a portion of our user-customizable interface in Cashboard.

seth, Sat, 23 Dec 2006 05:14:00 GMT
no comments

Upgrading to Debian Etch for Rails Mongrel Hosting

We need more power!

I just completed a major upgrade to our main production web host box.

Apache 2.0 / FCGI just wasn’t cutting it any more. I wanted speed, I wanted the new hotness…I wanted to run Mongrel. I was one of the first to sign onto Rimu Hosting, who originally set everything up.

Unfortunately I was also one of those who couldn’t install Ruby 1.8.4 / Mongrel / etc the preferred way for Debian Sarge as outlined here.

The backport way looked a little scary, and the ‘gotchas’ about having to install rubygems and zlib seemed too much to handle.

In the end I got it all done, but not without some problems. Good thing I didn’t have anything to do today…

From Debian Sarge to Etch (aka Debian Testing)

Being completely ignorant about what could happen I bit the bullet and started the upgrade to Debian Etch. (Well not completely ignorant, I backed up all my mail, sites, and databases first :)

Beginning the upgrade

This part was easy, I updated my /etc/apt/sources.list to look like this:


deb http://mirrors.easynews.com/linux/debian/ etch main contrib non-free
deb-src http://mirrors.easynews.com/linux/debian etch main contrib non-free

deb http://security.debian.org/ etch/updates main contrib non-free
deb-src http://security.debian.org/ etch/updates main
After that it was a matter of typing
  • sudo apt-get update
  • sudo dist-upgrade

Houston, we have a problem…

It went along its merry way until I started getting errors like this:


Errors were encountered while processing:
Unpacking openssh-server (from .../openssh-server_1%3a4.3p2-7_i386.deb) ...
perl: warning: Setting locale failed.
perl: warning: Please check that your locale settings:
        LANGUAGE = "en_US:en_GB:en",
        LC_ALL = (unset),
        LANG = "en_US" 
    are supported and installed on your system.
perl: warning: Falling back to the standard locale ("C").
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_ALL to default locale: No such file or directory
Transferring ownership of conffile /etc/default/ssh ...
Transferring ownership of conffile /etc/init.d/ssh ...
Transferring ownership of conffile /etc/pam.d/ssh ...
dpkg: error processing /var/cache/apt/archives/openssh-server_1%3a4.3p2-7_i386.deb (--unpack):
 trying to overwrite `/etc/init.d/ssh', which is also in package ssh
dpkg-deb: subprocess paste killed by signal (Broken pipe)
Aborting ownership transfer of conffile /etc/default/ssh ...
Aborting ownership transfer of conffile /etc/init.d/ssh ...
Aborting ownership transfer of conffile /etc/pam.d/ssh ...
Errors were encountered while processing:
 /var/cache/apt/archives/openssh-client_1%3a4.3p2-7_i386.deb
 /var/cache/apt/archives/openssh-server_1%3a4.3p2-7_i386.deb
E: Sub-process /usr/bin/dpkg returned an error code (1)

Oh boy, this didn’t look good at all. Locale was busted. I started freaking out, but Google came to the rescue with this tidbit of helpful information


setenv LANGUAGE en_us
setenv LANG en_us
apt-get install -f

That fixed that error, but trying to continue with the install (apt-get install -f) started giving me other errors about SSH not being able to properly install. Something weird about not being able to overwrite a configuration file.

Worse yet, I couldn’t SSH into or out of the server at this point. Thankfully I still had a connection up.

I started freaking out. I considered wiping the entire system and having the Rimu folks install a fresh Rails stack with FC6. I sent tons of emails inquiring about the possibilities….

...Then I got fed up with the prospects of changing OSes and decided to fix this damn thing.

You’ve got questions, we’ve got answers…

Freenode IRC #debian to the rescue. Someone pointed me in the direction of this command:


apt-get -o DPkg::Options::="--force-overwrite" install openssh-client openssh-server 

This fixed all my problems, and install went along without a hitch. SSH was hanging on overwriting a configuration file. With that out of the way everything seemed to start working again.

A pack of rabid Mongrels

Finally, we had a working system again. MySQL was responsive and Apache was up, even if all of my Rails sites previously running on FCGI were screwed.

I went through the steps outline here to get Mongrel up, running and configured. Again, another problem. I had configured everything properly (I thought), but I kept getting ‘403 Forbidden’ errors when trying to access my sites.

Stumped, I checked out Apache’s error_log:


[Mon Dec 18 02:46:02 2006] [warn] proxy: No protocol handler was valid for the URL /goodnews/GenSelectsPatentedPreconceptionGenderSelectionSystemExplanation. If you are using a DSO version of mod_proxy, make sure the proxy submodules are included in the configuration using LoadModule.

Not sure what to make of that, Google again came to the rescue with this helpful hint. It seems that by default Debian doesn’t include the proxy_balancer or proxy_http modules. I enabled them, restarted Apache, and everything was running smoothly.

One last thing

I was also lucky to find this great script for automatically starting your Mongrel clusters upon a server reboot. Check it out, you’ll probably find it useful.

Bedtime

It took all day, but I was able to get it done. All in all not so bad. My production box is humming along with Mongrel now, and all is right with the world.

Hopefully this article can help some of you out there that might be struggling with the same thing.

seth, Mon, 18 Dec 2006 07:05:00 GMT
1 comment

Cashboard is coming. Are you ready?

What could it be, what could it be? The promo site for our newest release just went live.


Click the logo to check it out.

seth, Sun, 17 Dec 2006 04:03:00 GMT
no comments
Older posts: 1 2