#220 PDFKit
- Download:
- source codeProject Files in Zip (109 KB)
- mp4Full Size H.264 Video (19.4 MB)
- m4vSmaller H.264 Video (10.9 MB)
- webmFull Size VP8 Video (24.7 MB)
- ogvFull Size Theora Video (26.8 MB)
Existen varias librerías para generar archivos PDF en Ruby. Una de las más populares es Prawn, que es una excelente manera de generar PDFs desde cero con Ruby y que ya estudiamos en el Episodio 153 [verlo, leero]. Sin embargo hay situaciones en las que nos sería mucho más fácil generar un PDF a partir de un documento HTML ya existente, que es el caso que veremos en este episodio.
La generación de PDFs a partir de HTML no es una idea precisamente nueva pero hasta ahora las soluciones disponibles costaban dinero, pero ha habido mucha actividad en este campo a partir de una solución propuesta por Jared Pace con su nueva librería PDFKit. La gema depende de wkhtmltopdf, una herramienta que utiliza el motor de WebKit para generar documentos PDF, así que para instalar PDFKit tenemos primero que instalar wkhtmltopdf. PDFKit ya viene con wkhtmltopdf así que, dependiendo de nuestro entorno, puede que esto no sea necesario.
La gema de PDFKit se instala de la forma habitual:
$ gem install pdfkit
Tras su instalación PDFKit puede generar un archivo PDF si se le pasa un archivo HTML o la dirección de una página web. La gema también viene con un middleware de Rack que se puede usar para generar PDFs a partir de cualquier página de un sitio simplemente añadiendo .pdf
a la URL. Este es el enfoque que usaremos en nuestra aplicación.
Creación de PDFs de facturas
La aplicación que vamos a usar para demostrar el uso de PDFKit es la misma con la que trabajamos en el episodio sobre Prawn. Debajo se muestra la página de pedido de una aplicación sencilla de comercio electrónico. Queremos añadir un enlace a esta página que permita descargar la versión en PDF del pedido, y para eso es para lo que vamos a usar PDFKit.
Lo primero que haremos será añadir una referencia a PDFKit en nuestra aplicación. Dado que estamos con una aplicación Rails 3 podemos modificar el archivo Gemfile
:
gem "pdfkit"
Y después, para asegurarnos de que la gema está instalada:
$ bundle install
A continuación tenemos que añadir el middleware. En una aplicación Rails 3 esto hay que hacerlo en el fichero /config/application.rb
(si estuviéramos con una aplicación Rails 2 iría en el fichero de configuración environment.rb
). Modificaremos el entorno para que la aplicación utilice el middleware de PDFKit.
require File.expand_path('../boot', __FILE__) require 'rails/all' # Auto-require default libraries and those for the current Rails environment. Bundler.require :default, Rails.env module Store class Application < Rails::Application config.secret_token = '6f22fa632e18b338b4babfa5fca632f5454fc97317cb52f372fa0f0fdd7f4d5bd95a060ff412c7230627b5c17906c9762c09208624bc1ab97f8d5344d8d4f467' config.filter_parameters << :password config.middleware.use "PDFKit::Middleware" end end
Podemos ejecutar rake middleware
para ver qué middlewares están siendo utilizados por nuestra aplicación, con lo que podemos comprobar si aparece el de PDFKit:
$ rake middleware (in /Users/eifion/rails/apps_for_asciicasts/ep220/store) use ActionDispatch::Static use Rack::Lock use ActiveSupport::Cache::Strategy::LocalCache use Rack::Runtime use Rails::Rack::Logger use ActionDispatch::ShowExceptions use ActionDispatch::RemoteIp use Rack::Sendfile use ActionDispatch::Callbacks use ActiveRecord::ConnectionAdapters::ConnectionManagement use ActiveRecord::QueryCache use ActionDispatch::Cookies use ActionDispatch::Session::CookieStore use ActionDispatch::Flash use ActionDispatch::ParamsParser use Rack::MethodOverride use ActionDispatch::Head use PDFKit::Middleware run Store::Application.routes
Como en el listado aparece PDFKit::Middleware
podemos continuar. Ya es posible añadir la extensión .pdf
a cualquier URL de nuestra aplicación para obtener una versión en PDF de la misma. Si hacemos así para una de las facturas de la aplicación veremos su versión en PDF:
Y eso es todo. Prácticamente sin tener que hacer nada hemos creado una versión PDF del pedido. Podría tener mejor aspecto, pero ya tenemos una base sobre la que trabajar.
Personalizando el aspecto de la factura en PDF
Lo primero que haremos con la factura es quitar el fondo azul de la versión en PDF. Tenemos que ser capaces de distinguir la versión PDF de forma que podamos aplicar diferentes reglas de estilo y esto puede hacerse modificando la llamada al middleware para que PDFKit utilice el tipo de medio impreso.
config.middleware.use "PDFKit::Middleware", :print_media_type => true
El fichero de layout de nuestra aplicación tenemos una etiqueta stylesheet_link_tag
que incluye la hoja de estilos application.css
. Por defecto, esta hoja tiene el tipo de medio screen
lo que significa que con el cambio que acabamos de hacer al middleware ninguno de los estilos de esa hoja será aplicado en la versión en PDF. Vamos a modificar la etiqueta stylesheet_link_tag
para que se corresponda con el tipo de medio all
, con lo o ue los estilos se aplicarán tanto a versión de pantalla (HTML) como a la de impresión (PDF).
<%= stylesheet_link_tag "application", :media => 'all' %>
Podríamos haber creado una hoja de estilos completamente separada para la hoja de impresión pero en vez de eso vamos a incluir las reglas que aplican para la versión de impresión en la misma hoja de estilos. Al final del archivo CSS podemos añadir una regla de tipo de medio, y las reglas definidas en dicha sección sólo serán aplicables a la vista en PDF. Por tanto para eliminar el fondo azul de nuestra vista de impresión podemos añadir la siguiente regla CSS al final de la hoja de estilos de nuestra aplicación.
@media print { body { background-color: #FFF; } #container { width: auto; margin: 0; padding: 0; border: none; } }
Cuando recarguemos la vista de página de nuestro pedido veremos que el fondo azul habrá desaparecido.
El PDF que tenemos ahora es muy parecido al que creamos en el episodio sobre Prawn, pero hemos tenido que escribir mucho menos código para conseguir el mismo resultado. Prawn tiene sus ventajas, porque nos da mucho más control sobre la forma en que la se genera el fichero PDF, especialmente en documentos con varias páginas. Con PDFKit si la tabla del pedido ocupase varias páginas tendríamos problemas para controlar la forma en la que se muestran en cada página las cabeceras y pies.
Control de saltos de página
Dicho esto, con PDFKit sí que podemos tener cierto control sobre los saltos de página. Si añadimos unos párrafos de texto al principio del pedido (para que la tabla quede más abajo) veremos que la tabla ahora aparece en dos páginas distintas.
Podemos evitar este problema modificando la hoja de estilos de impresión para que se inserte un salto de página justo antes de la tabla y que siempre aparezca en la parte de arriba de la página, con la propiedad page-break-before
.
@media print { body { background-color: #FFF; } #container { width: auto; margin: 0; padding: 0; border: none; } #line_items { page-break-before: always; } }
Si recargamos el documento PDF de nuevo, la tabla volverá a aparecer en su propia página.
A pesar de tener este control básico de los saltos de página con PDFKit, si nuestros documentos son más complicados deberemos quedarnos con Prawn.
Hay todavía un último cambio que queremos hacer antes de acabar este episodio. Se trata de añadir un enlace de la versión PDF en la página HTML de la factura. Primero añadiremos un enlace al archivo PDF al final de la página.
<p><%= link_to "Download Invoice (PDF)", order_path(@order, :format => "pdf") %></p>
Con esto veremos un enlace en el pedido en HTML a la versión PDF, pero este enlace también aparecerá en la versión PDF (cosa que no queremos) Podemos ocultar este enlace en el PDF dando un identificador al elemento de párrafo que contiene el enlace:
<p id="pdf_link"><%= link_to "Download Invoice (PDF)", order_path(@order, :format => "pdf") %></p>
Y modificando después la hoja de estilos para que oculte dicho enlace en la versión impresa.
@media print { body { background-color: #FFF; } #container { width: auto; margin: 0; padding: 0; border: none; } #line_items { page-break-before: always; } #pdf_link { display: none; } }
Ahora tendremos un enlace al PDF en la versión HTML de la factura, pero dicho no enlace no aparecerá en el PDF.
Una alternativa
Antes de cerrar este episodio mencionaremos una alternativa a PDFKit, Wicked PDF. Este plugin tiene un enfoque distinto del de PDFKit, aunque también usa wkhtmltopdf. Wicked PDF no es un middlware sino que es un renderer de Rails. Wicked PDF nos da más control sobre qué acciones pueden mostrarse como PDFs, por lo que es una opción que merece la pena tener en cuenta.
Y eso es todo por este episodio. Ya hemos cubierto dos maneras muy distintas de generar documentos PDF en nuestras aplicaciones Rails, cada una de ellas con sus ventajas y desventajas. Si bien PDFKit nos da la facilidad de generar PDFs a partir del HTML, con Prawn tendremos más control sobre los archivos generales. Hay que considerar bien ambos enfoques.