Crear un documento PDF en Python con borb

En esta guía, veremos cómo crear un documento PDF en Python, utilizando pText, una biblioteca para cargar, manipular y guardar documentos PDF, con ejemplos.

El Portable Document Format (PDF) no es un formato WYSIWYG (lo que ves es lo que obtienes). Fue desarrollado para ser independiente de la plataforma, independiente del sistema operativo subyacente y los motores de renderizado.

Para lograr esto, el PDF se construyó para interactuar a través de algo más parecido a un lenguaje de programación y se basa en una serie de instrucciones y operaciones para lograr un resultado. De hecho, PDF está basado en un lenguaje de secuencias de comandos: Posdata, que fue el primer lenguaje de descripción de página independiente del dispositivo.

Tiene operadores que modifican estados de gráficos, que, desde un alto nivel, se parecen a:

  • Establezca la fuente en "Helvetica"
  • Establecer el color del trazo en negro
  • Ir a (60.700)
  • Dibujar el glifo "H"

Esto explica algunas cosas:

  • Por qué es tan difícil extraer texto de un PDF sin ambigüedades
  • Por qué es difícil editar un documento PDF
  • Por qué la mayoría de las bibliotecas PDF imponen un enfoque de muy bajo nivel para la creación de contenido (usted, el programador, debe especificar las coordenadas en las que se representará el texto, los márgenes, etc.)

En esta guía, usaremos borracho, una biblioteca de Python dedicada a leer, manipular y generar documentos PDF, para crear un documento PDF. Ofrece un modelo de bajo nivel (que le permite acceder a las coordenadas y el diseño exactos si elige usarlos) y un modelo de alto nivel (donde puede delegar los cálculos precisos de márgenes, posiciones, etc. a un administrador de diseño) .

Echaremos un vistazo a cómo crear e inspeccionar un documento PDF en Python, usando borb, y cómo usar algunos de los LayoutElements para agregar códigos de barras y tablas.

Instalación de borb {#instalación de borb}

borb puede descargarse desde la fuente en GitHub, o instalarse a través de pip:

1
$ pip install borb

Crear un documento PDF en Python con borb

borb tiene dos clases de claves intuitivas: Documento y Página, que representan un documento y las páginas que contiene. Estos son el marco principal para crear documentos PDF.

Además, la clase ‘PDF’ representa una API para cargar y guardar los ‘Documentos’ que creamos.

Con eso en mente, creemos un archivo PDF vacío:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from borb.pdf.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF

# Create an empty Document
document = Document()

# Create an empty page
page = Page()

# Add the Page to the Document
document.append_page(page)

# Write the Document to a file
with open("output.pdf", "wb") as pdf_file_handle:
    PDF.dumps(pdf_file_handle, document)

La mayor parte del código habla por sí mismo aquí. Comenzamos creando un Documento vacío, luego agregamos una Página vacía al Documento con la función append(), y finalmente almacenamos el archivo a través de PDF.dumps().

Vale la pena señalar que usamos el indicador "wb" para escribir en modo binario, ya que no queremos que Python codifique este texto.

Esto da como resultado un archivo PDF vacío, llamado output.pdf en su sistema de archivos local:

documento pdf en blanco en python

Creación de un documento "Hello World" con borb

Por supuesto, los documentos PDF vacíos en realidad no transmiten mucha información. Agreguemos algo de contenido a la Página, antes de agregarlo a la instancia del Documento.

De manera similar a las dos clases integrales de antes, para agregar contenido a la Página, agregaremos un PageLayout que especifica el tipo de diseño que nos gustaría ver, y agregaremos uno o más Párrafos a ese diseño.

Con este fin, el ‘Documento’ es la instancia de nivel más bajo en la jerarquía de objetos, mientras que el ‘Párrafo’ es la instancia de nivel más alto, apilado sobre el ‘Diseño de página’ y, en consecuencia, la ‘Página’.

Agreguemos un Párrafo a nuestra Página:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from borb.pdf.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF
from borb.pdf.canvas.layout.paragraph import Paragraph
from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.io.read.types import Decimal

document = Document()
page = Page()

# Setting a layout manager on the Page
layout = SingleColumnLayout(page)

# Adding a Paragraph to the Page
layout.add(Paragraph("Hello World", font_size=Decimal(20), font="Helvetica"))

document.append_page(page)

with open("output.pdf", "wb") as pdf_file_handle:
    PDF.dumps(pdf_file_handle, document)

Notarás que agregamos 2 objetos adicionales:

  • Una instancia de PageLayout, más concreta a través de su subclase SingleColumnLayout: esta clase realiza un seguimiento de dónde se agrega contenido a una Página, qué áreas están disponibles para contenido futuro, qué Página son los márgenes, y lo que se supone que es el interlineado (el espacio entre los objetos Paragraph).

Como solo estamos trabajando con una columna aquí, estamos usando un SingleColumnLayout. Alternativamente, podemos usar MultiColumnLayout.

  • Una instancia Paragraph: esta clase representa un bloque de texto. Puede establecer propiedades como la fuente, tamaño_fuente, color_fuente y muchas otras. Para obtener más ejemplos, debe consultar la documentación.

Esto genera un archivo output.pdf que contiene nuestro Paragraph:

crear un pdf de hola mundo con python

Inspección del PDF generado con borb

Nota: Esta sección es completamente opcional si no está interesado en el funcionamiento interno de un documento PDF.

Pero puede ser muy útil saber un poco sobre el formato (como cuando estás depurando el problema clásico de "por qué ahora aparece mi contenido en esta página").

Por lo general, un lector de PDF leerá el documento comenzando por los últimos bytes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
xref
0 11
0000000000 00000 f
0000000015 00000 n
0000002169 00000 n
0000000048 00000 n
0000000105 00000 n
0000000258 00000 n
0000000413 00000 n
0000000445 00000 n
0000000475 00000 n
0000000653 00000 n
0000001938 00000 n
trailer
<</Root 1 0 R /Info 2 0 R /Size 11 /ID [<61e6d144af4b84e0e0aa52deab87cfe9><61e6d144af4b84e0e0aa52deab87cfe9>]>>
startxref
2274
%%EOF

Aquí vemos el marcador de fin de archivo (%%EOF) y la tabla de referencias cruzadas (típicamente abreviada como xref).

El xref está delimitado por los tokens "startxref" y "xref".

Una xref (un documento puede tener múltiples) actúa como una tabla de búsqueda para el lector de PDF.

Contiene el desplazamiento de bytes (comenzando en la parte superior del archivo) de cada objeto en un PDF. La primera línea de xref (0 11) dice que hay 11 objetos en esta xref, y que el primer objeto comienza en el número 0.

Cada línea subsiguiente consta del desplazamiento de bytes, seguido del llamado número de generación y la letra f o n:

  • Los objetos marcados con f son objetos libres, no se espera que sean renderizados.
  • Los objetos marcados con n están "en uso".

En la parte inferior de xref, encontramos el trailer dictionary. Los diccionarios, en sintaxis PDF, están delimitados por << y >>.

Este diccionario tiene los siguientes pares:

  • /Raíz 1 0 R
  • /Información 2 0 R
  • /Tamaño 11
  • /ID [<61e6d144af4b84e0e0aa52deab87cfe9> <61e6d144af4b84e0e0aa52deab87cfe9>]

El diccionario de avance es el punto de partida para el lector de PDF y contiene referencias a todos los demás datos.

En este caso:

  • /Root : este es otro diccionario que enlaza con el contenido real del documento.
  • /Info : este es un diccionario que contiene meta-información del documento (autor, título, etc).

Las cadenas como 1 0 R se denominan "referencias" en la sintaxis de PDF. Y aquí es donde la tabla xref resulta útil.

Para encontrar el objeto asociado con 1 0 R miramos el objeto 1 (número de generación 0).

La tabla de búsqueda xref nos dice que podemos esperar encontrar este objeto en el byte 15 del documento.

Si revisamos eso, encontraremos:

1
2
3
1 0 obj
<</Pages 3 0 R>>
endobj

Observe cómo este objeto comienza con 1 0 obj y termina con endobj. Esta es otra confirmación de que, de hecho, estamos tratando con el objeto 1.

Este diccionario nos dice que podemos encontrar las páginas del documento en el objeto 3:

1
2
3
4
3 0 obj
<</Count 1 /Kids [4 0 R]
 /Type /Pages>>
endobj

Este es el diccionario /Pages, y nos dice que hay 1 página en este documento (la entrada /Count). La entrada para /Kids suele ser una matriz, con una referencia de objeto por página.

Podemos esperar encontrar la primera página en el objeto 4:

1
2
3
4
4 0 obj
<</Type /Page /MediaBox [0 0 595 842]
 /Contents 5 0 R /Resources 6 0 R /Parent 3 0 R>>
endobj

Este diccionario contiene varias entradas interesantes:

  • /MediaBox: dimensiones físicas de la página (en este caso una página tamaño A4).
  • /Contents: referencia a un flujo (normalmente comprimido) de operadores de contenido PDF.
  • /Resources: referencia a un diccionario que contiene todos los recursos (fuentes, imágenes, etc.) utilizados para renderizar esta página.

Revisemos el objeto 5 para encontrar lo que realmente se representa en esta página:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
5 0 obj
<</Filter /FlateDecode /Length 85>>
stream
xÚã[correo electrónico protegido]
\È<§®ž`a¥£šÔw3T0„É
€!K¡š3B˜„žœenl7'§9©99ù
åùE9)š
!Y(’®!8õÂyšT*î
endstream
endobj

Como se mencionó anteriormente, este flujo (de contenido) está comprimido. Puede saber qué método de compresión utilizó la entrada /Filter. Si aplicamos la descompresión (unzip) al objeto 5, deberíamos obtener los operadores de contenido reales:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
5 0 obj
<</Filter /FlateDecode /Length 85>>
stream
            q
            BT
            0.000000 0.000000 0.000000 rg
            /F1 1.000000 Tf            
            20.000000 0 0 20.000000 60.000000 738.000000 Tm            
            (Hello world) Tj
            ET            
            Q
endstream
endobj

Finalmente, estamos en el nivel donde podemos decodificar el contenido. Cada línea consta de argumentos seguidos de su operador. Repasemos rápidamente los operadores:

  • q: conserva el estado gráfico actual (empujándolo a una pila).
  • BT: comienza el texto.
  • 0 0 0 rg: establece el color de trazo actual en (0,0,0) rgb. esto es negro
  • /F1 1 Tf: establece la fuente actual en /F1 (esta es una entrada en el diccionario de recursos mencionado anteriormente) y el tamaño de fuente en 1.
  • 20.000000 0 0 20.000000 60.000000 738.000000 Tm : establece la matriz de texto. Las matrices de texto garantizan una guía propia. Baste decir que esta matriz regula el tamaño de fuente y la posición del texto. Aquí estamos escalando la fuente a font-size 20 y configurando el cursor de dibujo de texto en 60,738. El sistema de coordenadas PDF comienza en la parte inferior izquierda de una página. Entonces 60,738 está en algún lugar cerca de la parte superior izquierda de la página (considerando que la página tenía 842 unidades de altura).
  • (Hello world) Tj : las cadenas en la sintaxis de PDF están delimitadas por ( y ). Este comando le dice al lector de PDF que represente la cadena "Hola mundo" en la posición que indicamos anteriormente con la matriz de texto, en la fuente, el tamaño y el color que especificamos en los comandos anteriores.
  • ET: texto final.
  • Q: extrae el estado de los gráficos de la pila (restaurando así el estado de los gráficos).

Adición de otros borb LayoutElements a las páginas

borb viene con una amplia variedad de objetos LayoutElement. En el ejemplo anterior exploramos brevemente Paragraph. Pero también hay otros elementos como UnorderedList, OrderedList, Image, Shape, Barcode y Table.

Vamos a crear un ejemplo un poco más desafiante, con una ‘Tabla’ y un ‘Código de barras’. Las Tablas consisten en TableCells, que añadimos a la instancia Table.

Un Barcode puede ser uno de los muchos BarcodeTypes - usaremos un código QR:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from borb.pdf.document import Document
from borb.pdf.page.page import Page
from borb.pdf.pdf import PDF
from borb.pdf.canvas.layout.paragraph import Paragraph
from borb.pdf.canvas.layout.page_layout import SingleColumnLayout
from borb.io.read.types import Decimal
from borb.pdf.canvas.layout.table import Table, TableCell
from borb.pdf.canvas.layout.barcode import Barcode, BarcodeType
from borb.pdf.canvas.color.color import X11Color

document = Document()
page = Page()

# Layout
layout = SingleColumnLayout(page)

# Create and add heading
layout.add(Paragraph("DefaultCorp Invoice", font="Helvetica", font_size=Decimal(20)))

# Create and add barcode
layout.add(Barcode(data="0123456789", type=BarcodeType.QR, width=Decimal(64), height=Decimal(64)))

# Create and add table
table = Table(number_of_rows=5, number_of_columns=4)

# Header row
table.add(TableCell(Paragraph("Item", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Unit Price", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Amount", font_color=X11Color("White")), background_color=X11Color("SlateGray")))
table.add(TableCell(Paragraph("Price", font_color=X11Color("White")), background_color=X11Color("SlateGray")))

    # Data rows
for n in [("Lorem", 4.99, 1), ("Ipsum", 9.99, 2), ("Dolor", 1.99, 3), ("Sit", 1.99, 1)]:
    table.add(Paragraph(n[0]))
    table.add(Paragraph(str(n[1])))
    table.add(Paragraph(str(n[2])))
    table.add(Paragraph(str(n[1] * n[2])))

# Set padding
table.set_padding_on_all_cells(Decimal(5), Decimal(5), Decimal(5), Decimal(5))
layout.add(table)

# Append page
document.append_page(page)

# Persist PDF to file
with open("output4.pdf", "wb") as pdf_file_handle:
    PDF.dumps(pdf_file_handle, document)

Algunos detalles de implementación:

  • borb admite varios modelos de color, incluidos: RGBColor, HexColor, X11Color y HSVColor.
  • Puede agregar objetos LayoutElement directamente a un objeto Table, pero también puede envolverlos con un objeto TableCell, esto le brinda algunas opciones adicionales, como establecer col_span y row_span o, en este caso, , color_de_fondo.
  • Si no se especifica font, font_size o font_color, Paragraph asumirá un valor predeterminado de Helvetica, size 12, black.

Esto resulta en:

creando un pdf de factura corporativa en python

Conclusión

En esta guía, hemos echado un vistazo a borb, una biblioteca para leer, escribir y manipular archivos PDF.

Hemos echado un vistazo a las clases clave como ‘Documento’ y ‘Página’, así como a algunos de los elementos como ‘Paragraph’, ‘Barcode’ y ‘PageLayout’. Finalmente, hemos creado un par de archivos PDF con diferentes contenidos, y también hemos inspeccionado cómo los archivos PDF almacenan datos debajo del capó.