I have a little Rails application that generates PDFs using Prawn. When I set out to write the code, I went looking for a renderer/template handler plugin that would let me construct PDFs using templates and partials. All I found was Prawnto, which appeared to be a bit… abandoned.
I thought about forking Prawnto or writing my own plugin, but I hadn’t written a plugin for Rails since the 2.3-ish, and I had no interest in subjecting myself to that again. Rather then writing my own Rails renderer, I decided I would just write my PDF rendering code as some well factored POROs, and that’s I went live with.
Over the holidays, I finally caught up on my reading which included Crafting Rails 4 Applications by José Valim. In that book he demonstrates how easy it is to add your own custom renderers and template handlers to a Rails application.
Starting with the code from the book, I was able to build a small plugin that could completely replace my PORO rendering in about a day. I spent the next morning incorporating the template rendering into my application, and then I pushed it to production on the first (Happy New Year!). That small plugin is PDFCraft, and I’m sharing it with you.
Really quickly, lets look at some example templates.
This is an exmple of a simple, single file template. The engine underlying Pdfcraft is Prawn, so that’s the API we use when constructing a document:
You’ll notice the reference to the PDF document is stored in the @pdf ivar. This works for our purposes, but I don’t especially like it. It will probably change in a future release.
Since Pdfcraft just plugs in to the Rails teamplate handling, you can also use partials.
The @pdf ivar isn’t exactly a Prawn::Document. Its a Pdfcraft document that delegates to the Prawn::Document. This is so we can extend the API. For example, here’s a template that renders in landscape.
By not depending explicitly on Prawn we leave the door open to use alternative PDF rendering engines. It would be spectacular to have a Streaming PDF renderer as an alternative engine. I haven’t found one yet, though.
Layouts, #render_to_string, and Other Gotchas
Layouts should work just fine. In fact, I built a layout with a #yield call, and all the content was rendered, but it overlapped. I think this is because layout templates are compiled at a different time then the other templates. I’ll need to do some more research.
The contract for #render_to_string declares that you must always return a string. However, PDFs are a binary format, and so a string represenation doesn’t make sense. Furthermore, Rails doesn’t have a #render_to_binary method. Due to these factors, I just leave #render_to_string returning the binary PDF data. If you want to cache your PDF content (to S3, for example), this is how you would get the data as a stream.
Finally. this is an early release of this gem. As noted, there might be some rough edges or unexpected behavior. If you try it out and find problems, feel free to report an issue.