lunes, 1 de marzo de 2010

Crear documentos PDF en Python, y 1.

En este post vamos a ver una pequeña introducción de cómo generar informes en formato PDF desde Python.

Para poder crear documentos en Python se pueden utilizar las librerías de ReportLab. Su web es esta: http://www.reportlab.com/software/opensource/

Puedes descargarte el software desde aquí:

http://www.reportlab.com/software/opensource/rl-toolkit/download/

El fichero es un ejectutable, y dependiendo del Python que tengas, tendrás que descargarte una versión u otra. Si necesitas incluir gráficos en tus documentos PDF debes de instalarte antes el Python Imaging Library (PIL). La dirección de descarga es: http://www.pythonware.com/products/pil/. Este también es un fichero ejecutable, lo instalas y listo.

Por tanto, instalamos PIL (si se necesita) y a continuación ReportLab.

¿Dónde encontrar documentación para empezar a trabajar con esta herramienta de creación de documentos PDF?
Todo lo que necesitas está aquí: http://www.reportlab.com/docs/reportlab-userguide.pdf

El paquete pdfgen es el nivel más bajo para generar documentos PDF. Un programa pdfgen es en realidad un conjunto de instrucciones para dibujar un documento en una secuencia de páginas.

La interfaz que provee de operaciones de dibujo es el objeto Canvas. El Canvas hay que verlo como una hoja de papel en blanco, donde tenemos coordenadas carterianas (X,Y), y el origen (0,0) se encuentra en la esquina inferior izquierda de la página. Incrementando X nos desplazamos a la derecha. Incrementando Y nos desplazamos hacia arriba.

Un ejemplo de la posición del Canvas, en Python, lo vemos a continuación:

# -*- coding: cp1252 -*-

from reportlab.pdfgen import canvas

aux = canvas.Canvas("prueba.pdf")

aux.drawString(0,0,"Posicion Original (X,Y) = (0,0)")
aux.drawString(50,100,"Posicion (X,Y) = (50,100)")
aux.drawString(150,20,"Posicion (X,Y) = (150,20)")

aux.showPage()
aux.save()

Este código crea un fichero prueba.pdf en el directorio de trabajo donde tenemos el fichero fuente de Python. El método showPage finaliza la "pintura" del la página actual. Todo el dibujo adicional que se quiera realizar se hará en otra página.

Si lo abrimos con un lector PDF (como Acrobat Reader) nos debe de aparecer el siguiente documento:

¿Fácil verdad? Veamos como incluir una imagen.

Opción 1: Utilizar el método drawImage(image, x, y, width = None, height = None) de la clase Canvas.

# -*- coding: cp1252 -*-

from reportlab.pdfgen import canvas

aux = canvas.Canvas("prueba1.pdf")

aux.drawImage("d:\\python\\pdf\\logo_blog.jpg",50,200,600,600)
aux.drawString(50,200,"EJEMPLO DE INSERCION DE IMAGEN EN PDF")

aux.showPage()
aux.save()

He insertado una imagen con los logotipos de lenguajes y plataformas y debajo un texto. Se guarda en prueba1.pdf. Darse cuenta que he configurado el ancho y alto de la imagen (width = 600, height = 600), para que me cupiera en el documento.

Opción 2: Utilizar los objetos Image(x, y, witdh, height, image) y Drawing(witdh, height) de las clasesImage y Drawing.

Esta segunda opción es mucho más potente que la primera, pero también algo más complicada. Aquíde lo que se trata es de crear una imagen con el objeto Image. Dicha imagen se incluye en un objeto Drawing, que es en realidad el que movemos por todo el papel, rotando, trasladando y redimensionando. Todas las operaciones que se realizan con objetos Drawing son sumativas. Lo vamos a ver claramente con el siguiente ejemplo.

Vamos a crear un documento, en donde vamos a incluir el logotipo de nuestra empresa, y los 3 posibles lugares que queremos que aparezcan en nuestros documentos tipo, a saber, en la esquina superior derecha del documento, en el centro del documento inclinado ligeramente y en el lado izquierdo del documento con inclinación vertical. El documento con las posibles posiciones de nuestro logotipo debe de quedar así:

El código que realiza esta operación es el siguiente:

# -*- coding: cp1252 -*-

from reportlab.graphics.shapes import Image, Drawing
from reportlab.graphics import renderPDF
from reportlab.lib.pagesizes import A4

imagenes = []

# Creamos imagen de logotipo de empresa, y lo colocamos en
# la esquina inferior derecha.
imagen = Image(400,0,130,150,"d:\\python\\pdf\logo_empresa.jpg")

# Creamos dibujo y le añadimos imagen.
dibujo = Drawing(30,30)
dibujo.add(imagen)

# Trasladamos el dibujo a la esquina superior derecha.
dibujo.translate(0,700)

# Incluimos en imágenes.
imagenes.append(dibujo)

# Volvemos a crear otro dibujo con logotipo de empresa. Darse cuenta
# que la imagen sigue en la esquina inferior derecha. Ahora incluimos
# en el dibujo la imagen.
dibujo = Drawing(30,30)
dibujo.add(imagen)

# Rotamos, con eje de rotación la esquina inferior izquierda (coordenadas (0,0))
# el dibujo.
dibujo.rotate(45)

# Y lo escalamos, haciéndolo un poquito mas grande.
dibujo.scale(1.5,0.5)

# Lo trasladamos un poco, para centrarlo.
dibujo.translate(-90,300)

# Y finalmente lo incluimos.
imagenes.append(dibujo)

# Volvemos a crear otro dibujo con logotipo de empresa. Darse cuenta
# que la imagen sigue en la esquina inferior derecha. Ahora incluimos
# en el dibujo la imagen.
dibujo = Drawing(30,30)
dibujo.add(imagen)

# Rotamos. Se nos puede salir de la hoja!
dibujo.rotate(90)

# Trasladamos a la izquierda del documento.
dibujo.translate(-20,-110)

# Incluimos.
imagenes.append(dibujo)

# Como vemos el objeto Drawing puede escalarse, rotarse y trasladarse, pero
# las operaciones son acumulativas.
dibujo = Drawing(A4[0],A4[1])
for aux in imagenes:
dibujo.add(aux)
renderPDF.drawToFile(dibujo, "prueba3.pdf")

Como vemos, lo primero que se hace es importar lo que necesitamos (¡ahora ya no utilizamos Canvas!).

from reportlab.graphics.shapes import Image, Drawing
from reportlab.graphics import renderPDF
from reportlab.lib.pagesizes import A4

El tamaño de las hoja se importa mediante from reportlab.lib.pagesizes import A4. A4[0] es el valor del ancho de la hoja A4 y A4[1] es el valor del alto del A4. Es importante comentar que hay varios tipos de hojas, a saber, A4, A3, A5, letter, etc).

A continuación cargamos la imagen, esto es, nuestro logotipo de empresa.

imagen = Image(400,0,130,150,"d:\\python\\pdf\logo_empresa.jpg")

Como se puede ver en el código creo la imagen con tamaño (witdh = 130, height = 150) y en la posición (400,0), esto es, en la esquina inferior derecha. ¿Por qué? Porque de esta manera me es más encillo trasladarlo por todo el papel.

El objeto imagen por sí no hace nada, ya que para poder acceder a él tenemos que incluirlo en un objeto Drawing. Es decir, hay que incluir el objeto imagen en un Drawing para poder rotarlo, trasladarlo ó escalarlo, de la siguiente manera:

dibujo = Drawing(30,30)
dibujo.add(imagen)

En este paso el documento tiene esta apariencia (no se dibuja nada, solamente represento donde debería de estar):

A continuación traslado el Drawing a la esquina superior derecha, con el siguiente código:

dibujo.translate(0,700)

Y queda así:

Puede entenderse ahora el significado de "sumativo". Para poder conseguir un resultado de posición en una imagen hay que ir sumando movimientos. Seguidamente guardamos el Drawing en la lista imagenes[] que hemos definido previamente (justo después de los import).

imagenes.append(dibujo)

Bien, hasta aquí hemos creado una imagen con el logotipo de nuestra empresa, lo hemos incluido en un Drawing, y este último es el que cambiamos de posición. El siguiente paso es crear otro logotipo, a partir de la misma imagen, para colorcarlo en el centro del documento, e inclinado hacia la izquierda.

dibujo = Drawing(30,30)
dibujo.add(imagen)

Es importante observar que el objeto imagen sigue en la esquina inferior derecha, ya que anteriormente no hemos movido la imagen, sino un Drawing. Esto es quizás donde hay más despiste!!! El objeto imagen no puede verse a no ser que esté incluido en un Drawing. ¡Ojo! Ahora estaríamos en esta situación:

¿Qué hay que hacer? Llevarnos la imagen de la esquina inferior derecha al centro. ¿Cómo? Mediante rotate podemos rotar la imagen, pasándole como argumento los grados que queremos que rote. La imagen no rota sobre sí misma, sino a partir del eje (0,0).

dibujo.rotate(45)

Lo hacemos más grande, redimensionándolo con scale.

dibujo.scale(1.5,0.5)

Y Finalmente, lo "movemos" al centro, mediante translate.

dibujo.translate(-90,300)

Y por supuesto lo incluimos en nuestra lista de imágenes.

imagenes.append(dibujo)

Debe de quedarnos algo parecido a esto:

Por útlimo nos toca llevar la imagen a la parte izquierda del documento. Volvemos a hacer las operaciones clásicas:

dibujo = Drawing(30,30)
dibujo.add(imagen)

Rotamos esta vez 90 grados (se nos saldrá de la hoja).
dibujo.rotate(90)

Trasladamos a la izquierda del documento. Ver como ahora x e y se intercambian, ya que al rotar 90 grados cambia para el Drawing su eje de coordenadas.

dibujo.translate(-20,-110)

Incluimos en lista de imágenes.

imagenes.append(dibujo)

Y finalmente terminamos... ¿Qué hacer ahora? Creamos un Drawing con tamaño de ancho lo mismo que un documento A4 (A4[0]) y de alto lo mismo que un documento A4 (A[1]).

dibujo = Drawing(A4[0],A4[1])

Y en este Drawing vamos incluyendo todos los Drawing que hemos creado, esto, es, los 3 logotipos, que se encuentran en la lista de imágenes.

for aux in imagenes:
dibujo.add(aux)

Llevamos el documento al fichero prueba3.pdf.

renderPDF.drawToFile(dibujo, "prueba3.pdf")

Y tenemos el resultado:

Decir que translate realiza un traslado a partir de la posición actual, esto es, posición original (x,y) y si lo traslado a una nueva posición (a, b) significa en realidad la nueva posición (x+a, y+b).
En el caso de la redimensión mediante scale, a partir de una posición (x,y), si quiero escalar (a,b) en realidad estoy llevando la imagen a una nueva posición (x*a, y*b) y con un tamaño de a * (ancho imagen) y b * (alto imagen). A continuación vemos los modos de movimientos posibles:

En el siguiente post sobre ReportLab hablaré como insertar texto.

7 comentarios:

  1. amigo como insertas un salto de pagina usando el canvas, osea cuando el pdf tenga mas de 1 pagina como lo tratas??

    ResponderEliminar
  2. Hola, buenas noches, para finalizar una página tienes que utilizar canvas.showPage(). Todo lo que después de este método escribas se escribirá en la siguiente página. Espero haberte ayudado. Un saludo.

    ResponderEliminar
  3. Buenos días,
    Debo decir que es el post más interesante que he encontrado sobre reportlab.
    Necesitaba hacerte una consulta ya que no encuentro a nadie que pueda ayudarme, te comento..
    Estoy intentando realizar un pequeño programa que me importe datos desde un archivo csv y me pinte los datos
    en una tabla con reportlab.La verdad es que no paro de encontrame impedimentos.
    1º Con los arrays.. ya que parece que no coje el array de datos

    data = []
    reader = csv.reader(open("Informe.csv", "rw"))
    for row in reader:
    uniline = unicode(row, 'latin-1')
    data.append(uniline)


    2º Con el problem que comentas con el unicode.. imposible que me coja el latin-1

    TypeError: coercing to Unicode: need string or buffer, list found

    3º con campos de la tabla, tengo un campo de la tablas algo más largo de lo normal y creo que no dejaría ajustarla.

    ¿Sabes si existe algún modulo ya escrito que haga eso?

    Un saludo.Muchas gracias

    ResponderEliminar
  4. Buenos días,
    Debo decir que es el post más interesante que he encontrado sobre reportlab.
    Necesitaba hacerte una consulta ya que no encuentro a nadie que pueda ayudarme, te comento..
    Estoy intentando realizar un pequeño programa que me importe datos desde un archivo csv y me pinte los datos
    en una tabla con reportlab.La verdad es que no paro de encontrame impedimentos.
    1º Con los arrays.. ya que parece que no coje el array de datos

    data = []
    reader = csv.reader(open("Informe.csv", "rw"))
    for row in reader:
    uniline = unicode(row, 'latin-1')
    data.append(uniline)


    2º Con el problem que comentas con el unicode.. imposible que me coja el latin-1

    TypeError: coercing to Unicode: need string or buffer, list found

    3º con campos de la tabla, tengo un campo de la tablas algo más largo de lo normal y creo que no dejaría ajustarla.

    ¿Sabes si existe algún modulo ya escrito que haga eso?

    Un saludo.Muchas gracias

    ResponderEliminar
  5. Excelente el post.
    Te hago una pregunta: ¿se puede modificar el tamaño de la fuente?

    gracias desde ya

    ResponderEliminar
  6. tengo problemas con los acentos, cuando cargo texto desde una base de dato, para mostrarlos en una tabla que esta en el archivo pdf a crear... como lo puedo soucionar?

    ResponderEliminar