Uso de borb con Python para crear libros electrónicos a partir del Proyecto Gutenberg

El formato de documento portátil (PDF) no es un formato WYSIWYG (Lo que ves es lo que obtienes). Fue desarrollado para ser agnóstico a la plataforma, independiente del sistema subyacente...

El Formato de documento portátil (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, PDF se construyó para poder 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.

En esta guía, utilizaremos borracho, una biblioteca de Python dedicada a leer, manipular y generar documentos 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) .

En esta guía, veremos cómo convertir un libro UTF-8 (del Proyecto Gutenberg) a un documento PDF.

{.icon aria-hidden=“true”}

Los libros electrónicos del Proyecto Gutenberg se pueden usar libremente en los Estados Unidos porque la mayoría no están protegidos por las leyes de derechos de autor de los Estados Unidos. Es posible que no estén libres de derechos de autor en otros países.

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

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

Para este proyecto también usaremos unidecode, es una pequeña biblioteca maravillosa que convierte texto de UTF-8 a ASCII. Tenga en cuenta que no todos los caracteres de UTF-8 se pueden representar como caracteres ASCII.

Esta es una conversión con pérdida, en principio, por lo que habrá alguna discrepancia cada vez que realice una conversión:

1
$ pip install unidecode

Crear un documento PDF con borb

La creación de un documento PDF usando borb normalmente sigue los mismos pasos cada vez:

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

import typing
import re

from borb.pdf.canvas.layout.page_layout.multi_column_layout import SingleColumnLayout
from borb.pdf.canvas.layout.page_layout.page_layout import PageLayout

# Create empty Document
pdf = Document()

# Create empty Page
page = Page()

# Add Page to Document
pdf.append_page(page)

# Create PageLayout
layout: PageLayout = SingleColumnLayout(page)

Creación de libros electrónicos con borb

{.icon aria-hidden=“true”}

Nota: Nos ocuparemos de libros de texto en bruto. Cada libro tendrá una estructura diferente y cada libro requiere un enfoque diferente para la representación. Esta es una tarea altamente subjetiva (estilo) y altamente dependiente del libro, sin embargo, el proceso general es el mismo.

El libro que descargaremos está codificado en UTF-8. No todas las fuentes admiten todos los caracteres. De hecho, la especificación de PDF define 14 fuentes estándar (que todo lector/escritor debería haber incorporado), ninguna de las cuales es compatible con el rango completo de UTF-8.

Entonces, para hacer nuestras vidas un poco más fáciles, vamos a usar esta pequeña función de utilidad para convertir un str a ASCII:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from unidecode import unidecode

def to_ascii(s: str) -> str:
    s_out: str = ""
    for c in s:
      if c == '“' or c == '”' or c == 'â':
        s_out += '"'
      else:
        s_out += unidecode(c)  
    return s_out

A continuación, en nuestro método principal, vamos a descargar el libro UTF-8.

En nuestro ejemplo, usaremos "The Mysterious affair at Styles" de Agatha Christie, que se puede obtener fácilmente en formato sin procesar desde Project Gutenberg:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# Define which ebook to fetch
url = 'https://www.gutenberg.org/files/863/863-0.txt'

# Download text
import requests
txt = requests.get(url).text
print("Downloaded %d bytes of text..." % len(txt))

# Split to lines
lines_of_text: typing.List[str] = re.split('\r\n', txt)
lines_of_text = [to_ascii(x) for x in lines_of_text]

# Debug
print("This ebook contains %d lines... " % len(lines_of_text))

Esto imprime:

1
2
Downloaded 361353 bytes of text...
This ebook contains 8892 lines...

Las primeras líneas de texto son un encabezado general agregado por Project Gutenberg. Realmente no queremos eso en nuestro libro electrónico, así que simplemente lo eliminaremos, verificando si una línea comienza con un cierto patrón y cortándola a través de la [notación de corte](/notacion-de-rebanada -de-python-en-cadena/):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Skip header
header_offset: int = 0
for i in range(0, len(lines_of_text)):
  if lines_of_text[i].startswith("*** START OF THE PROJECT GUTENBERG EBOOK"):
    header_offset = i + 1
    break
while lines_of_text[header_offset].isspace():
  header_offset += 1
lines_of_text = lines_of_text[header_offset:]
print("The first %d lines are the gutenberg header..." % header_offset)

Esto imprime:

1
The first 24 lines are the gutenberg header...

Del mismo modo, las últimas líneas de texto son solo un aviso de derechos de autor. También lo eliminaremos:

1
2
3
4
5
6
7
8
# Skip footer
footer_offset: int = len(lines_of_text)
for i in range(0, len(lines_of_text)):
    if "*** END OF THE PROJECT GUTENBERG EBOOK" in lines_of_text[i]:
      footer_offset = i
      break
lines_of_text = lines_of_text[0:footer_offset]
print("The last %d lines are the gutenberg footer .." % (len(lines_of_text) - footer_offset))

Con eso fuera del camino, vamos a procesar el cuerpo principal del texto.

Este código tomó un poco de prueba y error y si está trabajando con un libro diferente, también requerirá algo de prueba y error.

Averiguar cuándo insertar el título de un capítulo, cuándo comenzar un nuevo párrafo, cuál es el índice, etc. también depende del libro. Esta es una oportunidad para jugar un poco con borb e intentar analizar la entrada usted mismo con un libro diferente:

 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
49
50
51
52
53
54
from borb.pdf.canvas.layout.text.paragraph import Paragraph
from borb.pdf.canvas.layout.text.heading import Heading
from borb.pdf.canvas.color.color import HexColor, X11Color
from decimal import Decimal

# Main processing loop
i: int = 0
while i < len(lines_of_text):
  
    # Process lines
    paragraph_text: str = ""
    while i < len(lines_of_text) and not len(lines_of_text[i]) == 0:
      paragraph_text += lines_of_text[i]
      paragraph_text += " "
      i += 1

    # Empty line
    if len(paragraph_text) == 0:
      i += 1
      continue

    # Space
    if paragraph_text.isspace():
      i += 1
      continue

    # Contains the word 'CHAPTER' multiple times (likely to be table of contents)
    if sum([1 for x in paragraph_text.split(' ') if 'CHAPTER' in x]) > 2:
      i += 1
      continue

    # Debug
    print("Processing line %d / %d" % (i, len(lines_of_text)))

    # Outline
    if paragraph_text.startswith("CHAPTER"):
      print("Adding Header of %d bytes .." % len(paragraph_text))
      try:
        page = Page()
        pdf.append_page(page)
        layout = SingleColumnLayout(page)
        layout.add(Heading(paragraph_text, font_color=HexColor("13505B"), font_size=Decimal(20)))
      except:
        pass
      continue

    # Default
    try:
        layout.add(Paragraph(paragraph_text))
    except:
      pass
  
    # Default behaviour
    i += 1

Todo lo que queda es almacenar el documento PDF final:

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

creating pdf ebooks with borb

Conclusión

En esta guía, aprendió cómo procesar un texto grande y crear un PDF a partir de él automáticamente usando borb.

La creación de libros a partir de archivos de texto sin formato no es un proceso estándar, y tendrá que probar las cosas y jugar con los bucles y la forma en que trata el texto para hacerlo bien.