En la aplicación que estaba haciendo en RoR necesitaba generar documentos en formato pdf a partir de los formularios rellenados por el usuario. Había comenzado a hacerlos usando la librería para ruby PDF::Writer y aparentemente funcionaba bastante bien pero resultaba un infierno el tener que maquetar toda la presentación desde código, aparte de que no me gustaba mucho la idea de hacerlo así de cara a posibles cambios de la plantilla.
De casualidad, a través de un enlace en Devzone di con un árticulo sobre la generación de pdf's en java usando las librerías Flying Saucer e iText.
Flying Saucer es una librería para renderizar documentos xhtml y css 2.1 y que ahora trabaja de manera conjunta a iText, que sirve para generar documentos pdf.
Así que la opción de tratar de integrar esta librería en la aplicación para así convertir la vista generada desde Rails en el pdf, era más que tentadora.
Para hacerlo, lo primero era descargar la librería Flyin Saucer(que ya viene con iText incluida) y generar la aplicación en Java que se encargase de convertir el documento xhtml en pdf(está copiada casi tal cual de un ejemplo del artículo):
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.xhtmlrenderer.pdf.ITextRenderer;
import com.lowagie.text.DocumentException;
public class Xhtml2Pdf {
/**
* @param args
*/
public static void main(String[] args) throws IOException, DocumentException {
if (args.length != 2) return;
String inputFile = args[0];
String url = new File(inputFile).toURI().toURL().toString();
String outputFile = args[1];
OutputStream os = new FileOutputStream(outputFile);
ITextRenderer renderer = new ITextRenderer();
renderer.setDocument(url);
renderer.layout();
renderer.createPDF(os);
os.close();
}
}
Luego escribí una sencilla función en ruby que se encargase de ejecutar la clase hecha en java pasándole como parámetros el archivo de origen con el documento xhtml y el archivo de destino donde se generaría el pdf(lo único que tiene así de 'chicha' es que genera el classpath de manera dinámica):
def xhtml2pdf(input_file, output_file)
java_dir = File.join(File.expand_path(File.dirname(__FILE__)), "java")
jar_dir = File.join(java_dir, "jar")
class_path = ".:#{java_dir}"
Dir.foreach(jar_dir) do |jar|
class_path << ":#{jar_dir}/#{jar}" if jar.match(/\.jar/)
end
command = "java -cp #{class_path} Xhtml2Pdf #{input_file} #{output_file}"
system(command)
end
Para poder utilizar la librería desde el controlador, tan sólo quedaba copiar el fichero en ruby dentro la carpeta lib/ del proyecto Rails, la clase en java en lib/java y los jar's necesarios para ésta, en lib/java/jar:

Y en el controlador tan sólo tenía que generar la vista y guardarla en un fichero, convertirlo en pdf y mandarlo al cliente:
require 'xhtml2pdf'
class FooController < ApplicationController
def generate_pdf
@document = Document.find(params[:id])
xhtml = "/tmp/foo.xhtml"
pdf = "/tmp/foo.pdf"
File.open(xhtml, "w") do |file|
file << render_to_string(:template => "pdf/document", :layout => "../pdf/pdf")
end
xhtml2pdf(xhtml, pdf)
send_file pdf,
:filename => "document.pdf",
:type => "application/pdf"
end
end
Y si quisieramos ir viendo en el navegador cómo va a quedar sin necesidad de generar el pdf, bastaría con comentar el contenido de esta función(o hacer una nueva) y generar la vista como hariamos normalmente:
render :template => "pdf/document", :layout => "../pdf/pdf"
Por último, comentar que para que Flying Saucer pueda acceder a las imágenes y hojas de estilo del documento xhtml, las rutas de estos elementos deben estar de manera absoluta apuntando a un recurso local(file://<rutadelfichero>) o bien de manera relativa a dónde se encuentre físicamente el xhtml, por lo que igual habría que hacer un helper que modificase la ruta de los recursos dependiendo de si se está previsualizando desde el navegador o se está volcando en disco.
Aunque PDF::Writer parecía funcionar bastante bien e iba más rápido ya que escribe directamente el pdf sin necesidad de parsear un documento xhtml y sus hojas de estilo, creo que en este caso compensa tener un pequeño híbrido en rails y java por el tiempo ahorrado en maquetar el pdf directamente desde ruby.



