Вы находитесь на странице: 1из 5

DESARROLLO Python: Reportlab

Un vistazo a la librera Platypus

MAQUETANDO CON PYTHON


Es muy sencillo generar un fichero PDF con Reportlab y Python, pero realizar un documento complejo puede representar un reto que probablemente nos har perder una buena cantidad de horas. POR JOS MARA RUZ

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

Primeros Pasos con Platypus


Conocedores de que la mayora de los documentos PDF poseen un formato tra-

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

Python: Reportlab DESARROLLO

Figura 2: Creando un PDF usando Canvas. Figura 1: Eje de coordenadas en PDF.

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.

Las Plantillas en Platypus


Para poder generar cualquier documento complejo es fundamental el concepto de plantilla y flujo. Pensemos en la tpica pgina de un libro. Arriba probablemente encontraremos una cabecera con el nombre del libro en la pgina de la izquierda y el nombre del captulo o seccin en la derecha. Puede incluso que se encuentren subrayadas. Mientras tanto, abajo hallaremos el nmero de la pgina. Pgina tras pgina nos encontraremos con el mismo formato Qu es lo que cambia? Pues el texto que cada pgina contiene, el nmero de pgina y el nombre del captulo. Pero

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-

Listado 1: Generando un Fichero PDF Simple


01 # -*- coding: utf-8 -*02 03 from reportlab.pdfgen import canvas 04 from reportlab.lib.pagesizes import A4 05 06 def hola(c): 07 c.drawString(100,700, Linux Magazine te dice Hola!) 08 09 c = canvas.Canvas (hola-mundo.pdf, pagesize=A4) 10 hola(c) 11 c.showPage() 12 c.save()

Listado 2: Generando un Fichero PDF con Platypus


01 # -*- coding: utf-8 -*02 from reportlab. platypus import SimpleDocTemplate, Paragraph 03 from reportlab.lib. units import cm 04 from reportlab.lib. styles import getSampleStyleSheet 05 06 estilos = getSampleStyleSheet() 07 doc = SimpleDocTemplate (ejemplo2.pdf) 08 story = [Paragraph (Linux Magazine te dice Hola desde Platypus, estilos[Title]),] 09 10 doc.build(story)

WWW.LINUX- MAGAZINE.ES

Nmero 65

47

DESARROLLO Python: Reportlab

Figura 3: PDF creado con Platypus, aadiendo estilo al texto.

Figura 4: Diseo de la plantilla que vamos a crear.

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.

Listado 3: Generador de Libros


001 # -*- coding: utf-8 -*002 003 import os 004 import os.path 005 from reportlab. platypus import * 006 from reportlab. lib import enums, colors 007 from reportlab. lib.units import cm 008 from reportlab. lib.styles import * 009 from reportlab.lib. styles import ParagraphStyle as PS 010 from reportlab.lib.pagesizes import A4 011 from reportlab.pdfgen import canvas 012 013 class NumberedCanvas(canvas.Canvas): 014 015 def __init__(self, *args, **kwargs): 016 canvas.Canvas.__init__(self, *args, **kwargs) 017 self._saved_page_states = [] 018 019 def showPage(self): 020 self._saved_page_states. append(dict(self.__dict__)) 021 self._startPage() 022 023 def save(self): 024 num_pages = len(self._saved_page_states) 025 for state in self._saved_page_states: 026 self.__dict__.update(state) 027 self.draw_page_number (num_pages) 028 canvas.Canvas.showPage(self) 029 canvas.Canvas.save(self) 030 031 def draw_page_number(self, page_count): 032 self.setFont(Helvetica, 10) 033 self.drawString(10*cm, 034 1*cm, 035 {0} de {1}.format(self._pageNumber, 036 page_count)) 037 038 estilos ={texto: PS(name=texto, 039 alignment=enums.TA_LEFT, 040 fontName =Helvetica, 041 fontSize = 10, 042 leading = 14, 043 firstLineIndent = 20, 044 ), 045 046 047 048 049 050 051 052 053 titulo : PS(name=titulo, alignment=enums.TA_LEFT, fontName =Helvetica-Bold, fontSize = 10, leading = 14, ), cabecera: PS(name=texto, alignment=enums.TA_LEFT, fontName =Helvetica-Oblique, 054 fontSize = 10, 055 leading = 14, 056 ), 057 058 } 059 060 class PlantillaLibro (BaseDocTemplate): 061 062 def cabecera(self,canvas): 063 origen = Frame(self.MARGEN, 064 A4[1] - 2*cm, 065 A4[0] - (2* self.MARGEN), 066 1*cm, 067 id=cabecera, 068 showBoundary=self.mostrar) 069 070 story = [] 071 story.append(Paragraph(Don Quijote de la Mancha, 072 estilos[cabecera]))

48

Nmero 65

WWW.LINUX- MAGAZINE.ES

Python: Reportlab DESARROLLO

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

Figura 5: Detalle del pie de pgina generado.

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

Listado 3: Generador de Libros


073 074 origen.addFromList(story, canvas) 075 canvas.line(x1=self.MARGEN, 076 y1=A4[1] - 2*cm, 077 x2=A4[0] - (2* self.MARGEN), 078 y2=A4[1] - 2*cm) 079 080 def formato(self, canvas, doc): 081 canvas.saveState() 082 self.cabecera(canvas) 083 canvas.restoreState() 084 085 def __init__(self, filename, **kw): 086 087 apply(BaseDocTemplate. __init__, (self, filename), kw) 088 089 self.mostrar = 0 090 091 self.allowSplitting = 1 092 self.showBoundary = self.mostrar 093 self.MARGEN = 1.5*cm 094 095 ruta = os.path.dirname (os.path.realpath(__file__)) 096 ruta_logo = os.path. join(ruta,nuevo-logo-res.png) 097 098 self.logo = Image(ruta_logo, 099 width=2.00*cm*1.7, 100 height=1.15*cm*1.7) 101 self.logo.hAlign=RIGHT 102 self.logo.vAlign=TOP 103 104 frameDatos = Frame(self.MARGEN, 105 3.8*cm, 106 A4[0] - (self.MARGEN*2), 107 23*cm, 108 id=datos, 109 showBoundary=self.mostrar) 110 111 template = PageTemplate(pagina_normal, 112 [frameDatos], 113 self.formato) 114 115 self. addPageTemplates(template) 116 117 def libro_pdf(): 118 119 f = open(quijote-1.txt) 120 quijote = f.read() 121 f.close() 122 123 parrafos = quijote.split(\r\n\r\n) 124 story = [] 125 126 for p in parrafos : 127 128 if p != \r\n: 129 130 if p == p.upper(): 131 story.append(PageBreak()) 132 story.append(Paragraph(p, estilos[titulo])) 133 else: 134 p2 = p. replace(\r\n,<br/>) 135 story.append( Paragraph(p2, estilos[texto])) 136 story.append(Spacer(10,10)) 137 138 doc = PlantillaLibro (ejemplo3.pdf, pagesize=A4) 139 doc.build(story, canvasmaker=NumberedCanvas) 140 141 if __name__==__main__: 142 libro_pdf()

WWW.LINUX- MAGAZINE.ES

Nmero 65

49

DESARROLLO Python: Reportlab

Figura 6: Detalle del encabezado generado.

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.

>>> cm 28.346456692913385 >>> 12 * cm 340.15748031496059

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

Вам также может понравиться