Renderizando PDFs en Python con Poppler

Buenas… hoy voy a mostrar una forma de renderizar archivos PDF dentro de nuestras aplicaciones y desde Python, para casi cualquier plataforma con la estemos trabajando.

Introducción
Si bien cuando se arma una aplicación de escritorio se suele «llamar» a un software externo (Acrobat Reader, Fox-It, Evince, etc.) para que muestre el contenido de un archivo PDF, generalmente queda «feo»; nuestro programa pierde la interacción y el control de lo que el usuario hace con el mismo, implica superponer un programa arriba del otro, etc. Esto suele producir una experiencia dificultosa para el usuario de nuestro querido programa, y como corresponde, los programadores nos ponemos tristes. 🙁

Como una alternativa tenemos, en plataformas Windows, y estando el Acrobat Reader instalado previamente, la posibilidad de embeber en nuestro programa el mismo como un control ActiveX (hay otras alternativas) pero en el resto de las plataformas este problema no tenía solución (que yo conociera, al menos), ni siquiera algo más «liviano». El resultado de embeber todo el Acrobat Reader es más o menos lo que hacen los navegadores web cuando tienen el plugin de Adobe instalado y hacemos click en un link a un archivo PDF de la web:

(Después de 1 minuto, y si el usuario no cerró la ventana, aparece esto)

Hace unos días, en la lista de usuarios de las bibliotecas wxPython preguntaron si se podía renderizar un PDF dentro de una ventana del mismo programa, pero sobre Linux, y buscando la solución me encontré con un binding Python para Poppler.

Solución: Poppler-Python sobre Cairo
Veamos, Poppler es una biblioteca de código abierto desarrollada en C con el objetivo de renderizar (es decir, interpretar y dibujar) archivos PDF, siendo la evolución del viejo Xpdf. Es una pieza de software imprescindible en el escritorio de cualquier Sistema Operativo Libre, ya que es un trabajo bárbaro el que hace, y además es la piedra fundamental de programas como Evince y Okular (más allá de que el PDF es un formato de archivo universal). Y lo mejor es que dibuja sobre cualquier contexto de dibujo de Cairo, así que donde haya Cairo y compilador de C, puede estar Poppler*.

Hasta acá, bien, Poppler se puede utilizar desde C (como lo hace Evince, el visor de PDFs de Gnome). Pero ‘ete aquí que me encontré con estos bindings de Poppler para Python, y si bien el ejemplo que éstos incluyen utiliza PyGTK, en realidad, sólo necesita de un Canvas Cairo (GTK dibuja mucho sobre Cairo y provee mecanismos para que el desarrollador lo utilice también); es el mismo Cairo que incluye wxPython a partir de la versión 2.8.9.0 (aunque se puede utilizar con ctypes desde versiones anteriores a la 2.8.9).

Un poco de Código
En fin, voy a tratar de explicarlo mejor, esta es la manera de renderizar un PDF dentro de una aplicación PyGTK. Voy a pegar acá abajo lo relevante:

    def __init__(self):
        [...]
        self.document = poppler.document_new_from_file (uri, None)
        self.n_pages = self.document.get_n_pages()
        self.current_page = self.document.get_page(0)
        self.scale = 1
        self.width, self.height = self.current_page.get_size()
        [...]
        self.dwg = gtk.DrawingArea()
        self.dwg.set_size_request(int(self.width), int(self.height))
        self.dwg.connect("expose-event", self.on_expose)

[...]

    def on_expose(self, widget, event):
        cr = widget.window.cairo_create()
        cr.set_source_rgb(1, 1, 1)

        if self.scale != 1:
            cr.scale(self.scale, self.scale)

        cr.rectangle(0, 0, self.width, self.height)
        cr.fill()
        self.current_page.render(cr)

Como se puede ver, Poppler-Python se encarga de cargar el archivo, devolver la cantidad de páginas, etc., mientras que con GTK creamos el contexto de dibujo Cairo, y todo lo que hacemos es decirle a Poppler «dibujá acá, en este contexto Cairo».

Entonces me puse a jugar con wxPython para hacer lo mismo, !y funciona!

Acá está el ejemplo:

#!/usr/bin/env python
import wx
import wx.lib.wxcairo
import sys
import poppler

class MyFrame(wx.Frame):

    def __init__(self):
        wx.Frame.__init__(self, None, -1, "Cairo Test", size=(500,400))
        self.Bind(wx.EVT_PAINT, self.OnPaint)
        uri = "file://" + sys.argv[1]
        self.document = poppler.document_new_from_file (uri, None)
        self.n_pages = self.document.get_n_pages()

        self.current_page = self.document.get_page(0)
        self.scale = 1
        self.width, self.height = self.current_page.get_size()
        self.SetSize((self.width, self.height))

    def OnPaint(self, event):
        dc = wx.PaintDC(self)
        cr = wx.lib.wxcairo.ContextFromDC(dc)
        cr.set_source_rgb(1, 1, 1)

        if self.scale != 1:
            cr.scale(self.scale, self.scale)

        cr.rectangle(0, 0, self.width, self.height)
        cr.fill()
        self.current_page.render(cr)

if __name__=="__main__":
    app = wx.App()
    f = MyFrame()
    f.Show()
    app.MainLoop()

Y acá está la captura de pantalla obligada, luego de ejecutar «python ejemplo.py /path/al/archivo.pdf» (click para agrandar):

De esta manera, es posible utilizar Python, Poppler y wxPython (o PyGTK) para mostrar y manejar el contenido de un archivo PDF dentro de nuestra aplicación y en casi cualquier plataforma, sin recurrir a componentes de terceros. 🙂

Consideraciones:
Bueno, quisiera comentar que poppler-python parece ser un proyecto «joven», ya que todavía sólo está incluido en los repositorios de Ubuntu 9.04 en adelante. Con lo cual, en caso de utilizarlo en otras distribuciones y/o plataformas, hay que hacer un checkout desde sus repositorios y compilarlo; sus únicas dependencias son pycairo y poppler (0.10.x o posterior). Es muy sencillo hacerlo (el típico ./configure, make y make install), y no tuve ningún problema, utilizando Ubuntu 8.10.

En caso de querer utilizar Poppler-Python sobre el Cairo que incluye wxPython, lo mejor es utilizar una versión 2.8.9.x o superior de wxPython (ya con eso es suficiente, porque incluye Cairo en el paquete). Ubuntu 9.04 trae wxPython 2.8.9.1, Poppler 0.10.x y Poppler-Python en los repositorios, así que con un único apt-get ya tenemos todo lo necesario para hacer andar el ejemplo de arriba.

En mi caso, para probarlo en Ubuntu 8.10, tuve que compilar poppler desde los fuentes (traía la versión 0.8.x) y después compilar python-poppler, por un lado. Por el otro, para utilizar Cairo sobre wxPython 2.8.8.0 (el que viene en Ubuntu 8.10), llamé a Cairo con ctypes, como dice acá, y me quedó este ejemplo. Este procedimiento sería similar para cualquier otra distribución con versiones viejas de Poppler y/o de wxPython. Para la distribución de nuestro software todo se simplifica, ya que se puede usar tranquilamente Py2Exe, cx_Freeze o PyInstaller, por nombrar algunos. Pero eso es tema de otro post 😉

* Poppler denomina Frontend a las diferentes APIs que tiene para ser utilizado, entre ellas QT4 (base de KDE) y Glib (GTK principalmente, base de Gnome). Y tiene como backend (es decir, necesita para dibujar) no sólo a Cairo, sino que también puede dibujar sobre Splash (ignoro qué es, y Google no me devuelve nada relevante).

Bueno, espero que al menos le sirva a alguien, que lo aprovechen y lo mejoren!

Saludos
Marcelo


Comentarios

3 respuestas a «Renderizando PDFs en Python con Poppler»

  1. Te felicito muy buen articulo sigue asi.

  2. Gracias, no sabía que ya estaban en los repositorios de Ubuntu los bindings de Poppler para python y GTK. Me vienen al pelo para mostrar documentos PDF empotrados en mi aplicación pero… ¿sabes de algún widget ya listo para usar? Viendo la demo veo que no autoescala cuando se cambia el tamaño de la ventana.

    Buen blog, saludos,
    t00m

  3. No conozco de algún widget… pero tampoco te pierdas la 2da parte del artículo! 🙂

    https://blog.marcelofernandez.info/2010/04/renderizando-pdfs-en-python-con-poppler-ii/

    Saludos

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *