Академический Документы
Профессиональный Документы
Культура Документы
Valeriy Lebedev-Fotolia.com
Necesitas generar un documento PDF con Python? Entonces la eleccin es sencilla: debes usar Reportlab. Pero hay un problema, no tienes otra opcin que usar Reportlab!!! Por suerte o por desgracia, Reportlab es la nica librera en Python que nos permite generar documentos PDF de calidad. Si sigues cualquiera de los tutoriales que hay por la red aprenders cmo crear documentos sencillos, pero si quieres generar uno complejo, te encontrars con un gran escollo. Por alguna razn que no logro entender, la documentacin de Reportlab sobre generacin de documentos complejos con Platypus (su librera de alto nivel) es escasa y de mala calidad. He sufrido en mis propias carnes innumerables problemas, y ms de una vez he desistido. Pero hace poco reun el valor (y las ganas) para invertir una gran cantidad de horas en bucear por el cdigo fuente de Reportlab con el objetivo de adentrarme en los secretos de Platypus, y puedo decir que he conseguido cierto nivel de entendimiento que ahora voy a compartir con el lector.
PDF muy sencillo. Para ello vamos a hacer uso de libreras de bajo nivel que trabajan con PDF directamente. Reportlab nos permite trabajar con la hoja sobre la que vamos a escribir o dibujar como si fuese de un lienzo (canvas). Sobre l podemos hacer lo que queramos y existe libertad absoluta. Podemos escribir en cualquier posicin o dibujar sobre lo escrito. Esta forma de trabajar es ideal para la creacin de un documento complejo y a medida, pero posee muchos inconvenientes. Para comenzar, el eje de coordenadas es un poco extrao: el punto (0,0) est en la esquina inferior izquierda de la pgina (ver Figura 1). Esto implica que debemos hacer constantemente clculos para poder situar los objetos en las posiciones que deseamos. A este nivel tampoco existen conceptos como prrafo o mrgenes, y todo objeto debe ser encajado con cuidado para que no aplaste a otros. El resultado de este sencillo script se puede ver en la Figura 2. Reportlab nos ofrece otra forma ms cmoda para trabajar con los documentos PDF, la librera Platypus.
Haciendo Memoria
Comencemos por recordar cmo funciona Reportlab. En el Listado 1 figura el cdigo de un programa que genera un fichero
dicional, los desarrolladores de Reportlab crearon una librera de tratamiento de textos a la que llamaron Platypus (Ornitorrinco en castellano). Esta librera es de alto nivel, ya no trabajamos con coordenadas y los distintos objetos son capaces de posicionarse automticamente. Veamos un ejemplo sencillo en el Listado 2 (ver resultado en Figura 3). Platypus impone una serie de restricciones a nuestros documentos. Para comenzar, considera que cada hoja de un documento se corresponde con una plantilla y que dicha plantilla dispone de al menos un Frame (marco) en el que se contendr la informacin principal de la hoja. La plantilla se encarga de definir la posicin y formato de los Frames adems de decorar la pgina con cabeceras, logotipos, nmero de pgina y dems componentes. En nuestro caso he optado por algo simple para este primer ejemplo de Platypus, y hago uso de la clase SimpleDocTemplate que crea un frame que ocupa toda la pgina con unos pequeos mrgenes y al que pasamos como parmetro el nombre del fichero que queremos generar. Otro concepto importante en Platypus es el de story. Un story no es ms que una lista de objetos que descienden de la clase Flowable (fluible) con los que se relle-
46
Nmero 65
WWW.LINUX- MAGAZINE.ES
narn los Frames de un documento. Estos objetos pueden ser de muchos tipos, pero por el momento slo hemos empleado el ms basico, Paragraph, que representa un prrafo de texto. Este Flowable requiere como parmetros tanto el texto que vamos a mostrar como el estilo con el que lo haremos. Como este es un ejemplo simple, he optado por hacer uso de la funcin getSampleStyleSheet(), que devuelve un conjunto de estilos estndar muy comunes. El ltimo paso consiste en pasar el story al documento mediante el mtodo build() que construir el fichero PDF y lo guardar en un fichero con el nombre que pasamos al constructor de SimpleDocTemplate. Como podemos ver, no hemos tenido que especificar posiciones, ni hemos trabajado con funciones sobre un Canvas, sino que hemos empleado objetos que ya poseen un significado definido y que han decidido sus posiciones automticamente.
Ahora que ya hemos visto cmo crear un documento con Platypus, vamos a pasar a analizar cada uno de sus componentes principales y a profundizar en sus posibilidades. Para ver todas las posibilidades de Platypus emplearemos un programa real (ver Listado 3) que genera un documento PDF con el formato tradicional de un libro a partir de un texto del Proyecto Gutenberg.
todas las pginas tienen en comn tanto la tipografa como la posicin del encabezado o los mrgenes. El fin de las plantillas es reunir todos estos elementos comunes de forma que slo debamos preocuparnos por el contenido principal. Pero qu hacemos con el texto que va cambiando de una a otra pgina? Parte de este texto ser generado de forma automtica (como el nmero de la pgina), y por tanto ser trabajo de la plantilla. Pero el contenido principal de la pgina, el texto, debe ser independiente de la pgina o de la plantilla. A ese contenido independiente y principal es al que denominamos en Platypus story. Como vimos, no es ms que una lista de objectos descendientes de la clase Flowable. Platypus considera a estos objetos como un flujo de elementos que irn llenando pgina tras pgina todos los frames disponibles hasta que no queden ms elementos en el story.
La Funcin libro_pdf
En el Listado 3 definimos una clase llamada Plantilla que hereda de BaseDoc-
WWW.LINUX- MAGAZINE.ES
Nmero 65
47
template. Si nos fijamos en el cdigo principal del programa (el que figura en libro_pdf()), veremos que lo primero que haces es cargar los datos del fichero quijote-1.txt. Este fichero corresponde a las primeras pginas de la edicin de el Quijote que podemos encontrar en castellano en el proyecto Gutenberg [3]. Es un fichero de texto con formato mnimo. Lo nico que vamos a controlar es la aparicin de una frase o prrafo en maysculas, que indica la presencia de un ttulo de un captulo. Emplearemos un diseo
de pgina como el que aparece en la Figura 4. Creamos una variable que llamamos story que contendr una lista. Aunque en Reportlab siempre se habla de story, la variable puede tener cualquier nombre. A esta lista aadimos el texto empleando tanto Paragraph como PageBreak, que sirve para provocar un salto de pgina. Entre prrafo y prrafo aadimos otro Flowable llamado Spacer que nos permite introducir un elemento vaco de las dimensiones que le especifi-
quemos. De esta forma podemos separar elementos dentro del story. Como ya indicamos antes, en ningn momento especificamos posiciones, ser Platypus el encargado de decidir dnde irn los elementos, pero algunos Flowables como Spacer o PageBreak nos permiten cierto control. Por ltimo, creamos el documento, indicando que queremos producir hojas A4. Posteriormente empleamos el mtodo build() que ser el encargado de generar el fichero en ltima instancia.
48
Nmero 65
WWW.LINUX- MAGAZINE.ES
Es necesario pararse y explicar con cierto detenimiento la opcin canvasmaker de build(). Por defecto, Platypus trabaja sobre la clase Canvas que incorpora Reportlab, pero es posible emplear una clase diferente, siempre que respete el interfaz de Canvas. Una de las limitaciones de Canvas es que no es posible controlar el nmero total de pginas. Si queremos, como en nuestro cdigo, mostrar en el pie de pgina no slo el nmero de la misma sino adems el nmero total de pginas, Canvas ser de poca ayuda. Por ello, yo siempre suelo emplear una clase derivada de Canvas muy conocida en la comunidad Reportlab llamada NumberedCanvas. Esta clase dibuja tanto el nmero de la pgina como el total de pginas en el pie. La encontr en la receta Python que se podemos ver en la direccin que aparece en el Recurso 4. La empresa ActiveState mantiene un sitio web con recetas y trucos para lenguajes dinmicos que cualquiera puede enviar y consultar. Es un recurso muy importante para los que desarrollamos con Python. El resultado es el que se puede apreciar en la Figura 5. El cdigo que vemos en libro_pdf() sera todo el necesario para generar un libro si tenemos bien definida nuestra
plantilla y documento. Pasemos a ver cmo podemos hacer esto nosotros mismos.
La Clase PlantillaLibro
PlantillaLibro es una clase que hereda de BaseDocTemplate, una clase que podemos usar como base para cualquier documento Platypus. Si nos fijamos en el mtodo __init__(), veremos que es preciso pasar al constructor de BaseDocTemplate los parmetros que recibamos, puesto que ser l quien se encargue de
disponer los elementos en el story. Nuestra labor en esta clase ser crear el formato de cada pgina, as como posicionar los frames que se rellenarn con el story. Un documento se compone de una o ms plantillas de pgina. La revista que tienes en tus manos ahora mismo emplea distintas plantillas. Las pginas del artculo disponen de varios frames: las distintas columnas y zonas con recuadros de cdigo fuente son frames. Pero todo lo que los rodea (encabezado, pie, imagen
WWW.LINUX- MAGAZINE.ES
Nmero 65
49
de fondo) es trabajo del diseador o maquetador. En nuestro documento slo emplearemos una plantilla de pgina, que llamamos template, y que pasamos al mtodo addPageTemplates(). Como podemos ver, la plantilla posee tres parmetros. El primero es un nombre que nos permite identificarla de forma sencilla. Yo he escogido llamarla pagina_normal. El segundo parmetro es una lista de Frames por los que el story fluira, mientras que el tercer parmetro ser un callable (una funcin o una clase que implemente el mtodo __call__()) que se llamar cada vez que se vaya a generar una pgina.
Vemos que el valor de inch es 72, porque 72 puntos por pulgada es la resolucin o densidad que posee un documento PDF por defecto. Tambin podemos recurrir, como en la creacin del documento, a la librera reportlab.lib.pagesizes, que nos ofrece distintos tamaos de pgina:
>>> from U reportlab.lib.pagesizes U import A4 >>> A4 (595.27559055118104, U 841.88976377952747) >>> A4[0] 595.27559055118104 >>> A4[1] 841.88976377952747
Los Frames
Definimos un Frame llamado frameDatos que emplearemos para disponer el texto en la pgina. La clase Frame necesita cuatro parmetros para definir tanto su posicin (coordenadas canvas X e Y) como su tamao (ancho y alto). Podemos emplear distintas unidades de medida para ello, siendo la unidad bsica el punto. Es recomendable utilizar unidades que podamos medir con facilidad, y para ello podemos hacer uso de la librera reportlab.lib.units que nos proporciona cm (centmetros), mm (milmetros) o inch (pulgadas). Slo tenemos que multiplicar el valor de la unidad por cualquiera de estas variables:
>>> from reportlab.lib.units U import cm,mm,inch >>> inch 72.0 >>> mm 2.8346456692913389
pgina con la numeracin se encarga NumberedCanvas. La funcin formato() recibe dos parmetros: el canvas y el documento sobre el que se est trabajando. Debido a que estamos fuera del flujo de story, trabajaremos a bajo nivel, fuera de Platypus, lo que nos permite posicionar elementos en el canvas en cualquier sitio. Lo primero que hacemos es hacer uso de los mtodos saveState() y restoreState(). Este par de mtodos nos permiten aislar nuestro cdigo del resto, haciendo una copia de todos los parmetros del canvas para recuperarlos posteriormente. De esta forma, cualquier modificacin que hagamos no surtir efecto ms all de la funcin formato(). Llamamos al mtodo cabecera() que genera un Frame y trabaja como si estuviese en su propio documento: crea prrafos y los introduce en su story particular. El problema que surge ahora es si no estamos trabajando sobre el documento cmo vamos a renderizar este story? La solucin es el mtodo addFromList de Frame, que hace algo parecido al mtodo build(): acepta un story y lo renderiza emplendose a s mismo como frame en el canvas que pasamos como parmetro. Si quisiramos dibujar o mostrar otra informacin en la pgina (por ejemplo, la fecha de generacin de documento en un lateral) deberamos crear un mtodo parecido a cabecera() e invocarlo desde formato(). Acabaremos con un encabezado como el que aparece en la Figura 6.
Conclusin
Platypus nos permite llevar la generacin de ficheros PDF a otro nivel, permitindonos generar documentos profesionales desde Python de forma sencilla. A pesar de su, en demasiadas ocasiones, confusa documentacin, Platypus es un recurso fundamental en el arsenal de cualquier desarrollador Python. I
A4 es en realidad una tupla con el ancho y el alto expresados en puntos. Podemos acceder a ambos valores como lo haramos con cualquier tupla o lista en Python. Frame dispone tambin de un identificador, y mediante la variable showBoundary podemos hacer que al ser renderizado aparezca un recuadro negro alrededor, extremadamente til en el proceso de diseo de una plantilla.
RECURSOS
[1] Librera Reportlab: http://www. reportlab.com/software/opensource/ [2] ActiveState code: activestate.com/
http://code.
Dibujando la Plantilla
La funcin formato() ser la encargada de dibujar la plantilla. Slo lo haremos con el encabezado, puesto que del pie de
[3] Don Quijote de la Mancha: http:// www.gutenberg.org/cache/epub/ 2000/pg2000.txt [4] NumberedCanvas: http://code. activestate.com/recipes/576832/
50
Nmero 65
WWW.LINUX- MAGAZINE.ES