使用borb从古腾堡计划创建电子书

376 阅读4分钟

该 ***可移植文档格式(PDF)***并不是一种 ***所见即所得(WYSIWYG)***格式。它被开发成与平台无关,与底层操作系统和渲染引擎无关。

为了实现这一点,PDF的构造是通过更像编程语言的东西进行交互,并依靠一系列的指令和操作来实现一个结果。事实上,PDF是基于一种脚本语言--PostScript,它是第一个独立于设备的页面描述语言

在本指南中,我们将使用 borb- 一个专门用于阅读、操作和生成 PDF 文档的 Python 库。它提供了一个低级别的模型(如果你选择使用精确的坐标和布局,允许你访问这些)和一个高级别的模型(你可以将边距、位置等的精确计算委托给一个布局管理器)。

在本指南中,我们将看看如何将一本UTF-8的书(来自古腾堡项目)转换为PDF文档。

古腾堡计划的电子书在美国可以自由使用,因为大多数不受美国版权法的保护。它们在其他国家可能不受版权保护。

安装borb

borb可以从GitHub上的源代码下载,或通过pip

$ pip install borb

安装unidecode

在这个项目中,我们还将使用unidecode ,它是一个奇妙的小库,可以将文本从UTF-8转换为ASCII。请记住,并不是UTF-8中的每个字符都可以表示为ASCII字符。

这是一种有损的转换,原则上来说,每次转换都会有一些差异。

$ pip install unidecode

borb创建一个PDF文档

使用borb创建PDF文档通常每次都遵循相同的步骤。

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)

用borb创建电子书

**注意:**我们要处理的是原始文本书籍。每本书都会有不同的结构,每本书需要不同的渲染方法。这是一个高度主观(造型)和高度依赖书籍的任务,虽然,一般的过程是相同的。

我们要下载的书是UTF-8编码的。不是每一种字体都支持每一个字符。事实上,PDF规范定义了14种标准字体(每个读者/书写者都应该嵌入这些字体),其中没有一种字体支持完整的UTF-8范围。

因此,为了使我们的生活更容易一些,我们将使用这个小的实用函数来将str 转为ASCII。

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

接下来,在我们的主方法中,我们将下载UTF-8的书。

在我们的例子中,我们将使用阿加莎-克里斯蒂的*"The Mysterious affair at Styles"*,它可以很容易地从古腾堡计划中获得原始格式。

# 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))

这个打印出来。

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

第一行文字是古腾堡项目添加的一个普通标题。我们并不希望在我们的电子书中出现这样的内容,所以我们要简单地删除它,方法是检查某一行是否以某种模式开始,并通过切分符号将其切分掉。

# 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)

这样就打印出来了。

The first 24 lines are the gutenberg header...

同样地,最后几行文字只是一个版权声明。我们也要删除它。

# 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))

说完这个,我们就来处理正文了。

这段代码需要一些试验和错误,如果你使用的是不同的书--也会需要一些试验和错误。

弄清楚什么时候插入章节标题,什么时候开始一个新段落,什么是目录,等等,也取决于书。这是一个玩转borb的机会,可以尝试自己用不同的书来解析输入。

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

剩下的就是存储最终的PDF文档了。

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

creating pdf ebooks with borb

结论

在本指南中,你已经学会了如何使用borb处理一大段文本并自动创建一个PDF。

从原始文本文件创建书籍不是一个标准的过程,你必须测试一下,玩玩循环和处理文本的方式,才能得到正确的结果。