该 ***可移植文档格式(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)
结论
在本指南中,你已经学会了如何使用borb处理一大段文本并自动创建一个PDF。
从原始文本文件创建书籍不是一个标准的过程,你必须测试一下,玩玩循环和处理文本的方式,才能得到正确的结果。