教程#
本教程将逐步向您展示如何在 Python 中使用 PyMuPDF(MuPDF)。
因为 MuPDF 不仅支持 PDF,还支持 XPS、OpenXPS、CBZ、CBR、FB2 和 EPUB 格式,所以 PyMuPDF 也支持这些格式 [1]。尽管如此,为简洁起见,我们将只讨论 PDF 文件。在确实只支持 PDF 文件的地方,将明确提及。
除了本介绍外,请务必访问 PyMuPDF 的 YouTube 频道,该频道以 YouTube“短视频”和长视频的形式涵盖了以下大部分内容。
导入绑定#
通过此导入语句,可以使用 MuPDF 的 Python 绑定。我们还在下方展示了如何检查您使用的版本
>>> import pymupdf
>>> print(pymupdf.__doc__)
PyMuPDF 1.16.0: Python bindings for the MuPDF 1.16.0 library.
Version date: 2019-07-28 07:30:14.
Built for Python 3.7 on win32 (64-bit).
关于名称 fitz 的说明#
旧版本的 PyMuPDF 的 Python 导入名称是 fitz
。新版本使用 pymupdf
,并提供 fitz
作为回退,以便旧代码仍然可用。
名称 fitz
的原因是一个历史趣闻
MuPDF 的原始渲染库名为 Libart。
“Artifex Software 收购 MuPDF 项目后,开发重点转向编写一个名为“Fitz”的新型现代图形库。Fitz 最初是作为取代老旧的 Ghostscript 图形库的研发项目,但后来成为了驱动 MuPDF 的渲染引擎。”(引自 维基百科)。
注意
如果安装了已废弃的 pypi.org 包 fitz
,则使用遗留名称 fitz
可能会失败;请参阅 安装后的问题。
打开文档#
要访问支持的文档,必须使用以下语句将其打开
doc = pymupdf.open(filename) # or pymupdf.Document(filename)
这将创建 Document 对象 doc。filename 必须是一个 Python 字符串(或 pathlib.Path
),指定现有文件的名称。
也可以从内存数据中打开文档,或创建一个新的空白 PDF。详情请参阅 Document。您还可以将 Document 用作上下文管理器。
文档包含许多属性和函数。其中包括元信息(如“作者”或“主题”)、总页数、大纲和加密信息。
一些 Document 方法和属性#
方法 / 属性 |
描述 |
---|---|
页数 (int) |
|
元数据 (dict) |
|
获取目录 (list) |
|
读取一个 Page 对象 |
访问元数据#
PyMuPDF 完全支持标准元数据。Document.metadata
是一个 Python 字典,包含以下键。它适用于所有文档类型,尽管并非所有条目都始终包含数据。有关其含义和格式的详细信息,请参阅相应的MANUALS,例如 PDF 的Adobe PDF 参考。更多信息也可在Document一章中找到。元数据字段是字符串,如果未另行说明,则为 None
。另请注意,并非所有字段都始终包含有意义的数据——即使它们不是 None
。
键 |
值 |
---|---|
producer |
生成软件 |
format |
格式: 'PDF-1.4', 'EPUB', 等 |
encryption |
使用的加密方法(如果有) |
author |
author |
modDate |
最后修改日期 |
keywords |
keywords |
title |
title |
creationDate |
创建日期 |
creator |
创建应用程序 |
subject |
subject |
注意
除了这些标准元数据之外,从 PDF 1.4 版本开始的 PDF 文档还可能包含所谓的“元数据流”(另请参阅stream
)。这些流中的信息采用 XML 编码。PyMuPDF 特意不包含用于此目的的 XML 组件(PyMuPDF Xml 类是一个帮助类,旨在访问Story对象的 DOM 内容),因此我们不直接支持访问其中包含的信息。但是您可以提取整个流,使用像 lxml 这样的包对其进行检查或修改,然后将结果存回 PDF。如果您愿意,也可以完全删除这些数据。
注意
仓库中有两个实用脚本,分别用于从 CSV 文件导入元数据(仅限 PDF)和将元数据导出到 CSV 文件。
使用大纲#
获取文档所有大纲(也称“书签”)的最简单方法是加载其目录
toc = doc.get_toc()
这将返回一个 Python 列表的列表 [[lvl, title, page, …], …],它看起来很像书籍中常见的目录。
lvl 是条目的层级(从 1 开始),title 是条目的标题,page 是页码(基于 1!)。其他参数描述了书签目标的详细信息。
注意
仓库中有两个实用脚本,分别用于从 CSV 文件导入目录(仅限 PDF)和将目录导出到 CSV 文件。
使用页面#
页面处理是 MuPDF 功能的核心。
您可以将页面渲染为栅格或矢量 (SVG) 图像,并可选择进行缩放、旋转、平移或倾斜。
您可以提取页面的文本和图像,支持多种格式,并可以搜索文本字符串。
对于 PDF 文档,还有更多方法可用于向页面添加文本或图像。
首先,必须创建一个 Page 对象。这是 Document 的一个方法
page = doc.load_page(pno) # loads page number 'pno' of the document (0-based)
page = doc[pno] # the short form
这里可以使用任何整数 -∞ < pno < page_count
。负数从末尾倒数,因此 doc[-1] 是最后一页,与 Python 序列类似。
一种更高级的方式是使用文档作为其页面的迭代器
for page in doc:
# do something with 'page'
# ... or read backwards
for page in reversed(doc):
# do something with 'page'
# ... or even use 'slicing'
for page in doc.pages(start, stop, step):
# do something with 'page'
一旦您获得了页面对象,以下是您通常会对其执行的操作
检查页面的链接、注解或表单字段#
当使用某些查看软件显示文档时,链接会显示为“热区”。如果您的光标显示为手形符号时点击,通常会被带到热区中编码的目标。以下是获取所有链接的方法
# get all links on a page
links = page.get_links()
links 是一个 Python 字典列表。详情请参阅 Page.get_links()
。
您也可以使用一个迭代器,它一次发出一个链接
for link in page.links():
# do something with 'link'
如果处理 PDF 文档页面,可能还存在注解(Annot)或表单字段(Widget),它们各自都有自己的迭代器
for annot in page.annots():
# do something with 'annot'
for field in page.widgets():
# do something with 'field'
渲染页面#
这个示例创建了一个页面的栅格图像
pix = page.get_pixmap()
pix
是一个 Pixmap 对象,它(在本例中)包含页面的 RGB 图像,可用于多种目的。Page.get_pixmap()
方法提供了许多控制图像的变体:分辨率/DPI、色彩空间(例如生成灰度图像或减色方案图像)、透明度、旋转、镜像、平移、倾斜等。例如:要创建 RGBA 图像(即包含 Alpha 通道),请指定 pix = page.get_pixmap(alpha=True)。
一个 Pixmap 包含许多方法和属性,如下所示。其中包括整数 width
、height
(均以像素为单位)和 stride
(一个水平图像行的字节数)。属性 samples
表示一个矩形字节区域,代表图像数据(一个 Python bytes
对象)。
注意
您也可以使用 Page.get_svg_image()
创建页面的矢量图像。详细信息请参阅此矢量图像支持页面。
将页面图像保存到文件#
我们可以简单地将图像存储到 PNG 文件中
pix.save("page-%i.png" % page.number)
在 GUI 中显示图像#
我们也可以在 GUI 对话框管理器中使用它。Pixmap.samples
代表所有像素的字节区域,是一个 Python bytes 对象。以下是一些示例,更多示例请参见 examples 目录。
wxPython#
有关 RGB(A) 像素图的调整以及可能与您的 wxPython 版本相关的具体信息,请查阅其文档
if pix.alpha:
bitmap = wx.Bitmap.FromBufferRGBA(pix.width, pix.height, pix.samples)
else:
bitmap = wx.Bitmap.FromBuffer(pix.width, pix.height, pix.samples)
Tkinter#
也请参阅 Pillow 文档的第 3.19 节
from PIL import Image, ImageTk
# set the mode depending on alpha
mode = "RGBA" if pix.alpha else "RGB"
img = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
tkimg = ImageTk.PhotoImage(img)
以下示例避免使用 Pillow
# remove alpha if present
pix1 = pymupdf.Pixmap(pix, 0) if pix.alpha else pix # PPM does not support transparency
imgdata = pix1.tobytes("ppm") # extremely fast!
tkimg = tkinter.PhotoImage(data = imgdata)
如果您正在寻找一个完整的 Tkinter 脚本来分页显示任何支持的文档,请看这里!。它还可以缩放页面,并且可以在 Python 2 或 3 下运行。它需要极其方便的纯 Python 包 PySimpleGUI。
PyQt4, PyQt5, PySide#
也请参阅 Pillow 文档的第 3.16 节
from PIL import Image, ImageQt
# set the mode depending on alpha
mode = "RGBA" if pix.alpha else "RGB"
img = Image.frombytes(mode, [pix.width, pix.height], pix.samples)
qtimg = ImageQt.ImageQt(img)
同样,您也可以不使用 Pillow。Qt 的 QImage
幸运地支持原生 Python 指针,因此以下是创建 Qt 图像的推荐方式
from PyQt5.QtGui import QImage
# set the correct QImage format depending on alpha
fmt = QImage.Format_RGBA8888 if pix.alpha else QImage.Format_RGB888
qtimg = QImage(pix.samples_ptr, pix.width, pix.height, fmt)
提取文本和图像#
我们还可以以多种不同的形式和详细程度提取页面的所有文本、图像和其他信息
text = page.get_text(opt)
为 opt 使用以下字符串之一以获取不同的格式 [2]
“text”:(默认)带换行符的纯文本。无格式,无文本位置详情,无图像。
“blocks”:生成文本块(= 段落)列表。
“words”:生成单词(不包含空格的字符串)列表。
“html”:创建页面的完整可视化版本,包括任何图像。可以使用您的互联网浏览器显示。
“dict” / “json”:信息级别与 HTML 相同,但分别以 Python 字典或 JSON 字符串的形式提供。有关其结构的详细信息,请参阅
TextPage.extractDICT()
。“rawdict” / “rawjson”:是 “dict” / “json” 的超集。它另外提供了像 XML 那样的字符详细信息。有关其结构的详细信息,请参阅
TextPage.extractRAWDICT()
。“xhtml”:文本信息级别与 TEXT 版本相同,但包含图像。也可以由互联网浏览器显示。
“xml”:不包含图像,但包含精确到每个文本字符的完整位置和字体信息。使用 XML 模块进行解释。
为了让您了解这些替代方案的输出,我们提供了文本提取示例。请参阅附录 1:文本提取详情。
搜索文本#
您可以准确地查出某个文本字符串在页面上出现的位置
areas = page.search_for("mupdf")
这将返回一个矩形列表(参见 Rect),每个矩形包围着字符串“mupdf”的一个出现(不区分大小写)。您可以利用这些信息来例如高亮显示这些区域(仅限 PDF)或创建文档的交叉引用。
请参阅章节 协同工作:DisplayList 和 TextPage 以及演示程序 demo.py 和 demo-lowlevel.py。它们详细介绍了如何使用 TextPage、Device 和 DisplayList 类进行更直接的控制,例如在性能考虑建议这样做时。
Stories:从 HTML 源代码生成 PDF#
The Story 类是 PyMuPDF 1.21.0 版本的新特性。它代表了对 MuPDF “story” 接口的支持。
以下是 Artifex 的 Robin Watts 所著书籍 “MuPDF Explored” 中的一段引用
Stories 提供了一种轻松排版带样式内容的方式,以便与设备(例如 Document Writers 提供的设备)一起使用(…)。Story 的概念源自桌面出版,而桌面出版(…)又源自报纸。如果你考虑传统的报纸版式,它将由布局在多列、可能跨越多页的各种新闻文章(stories)组成。
因此,MuPDF 使用 story 来表示带有样式信息的文本流。story 的用户可以提供一系列矩形,story 将在这些矩形中进行布局,然后将定位好的文本绘制到输出设备。这使得文本本身(story)的概念与文本应该流动的区域(布局)分开。
注意
Story 的工作方式有点像互联网浏览器:它忠实地解析和渲染 HTML 超文本以及可选的样式表 (CSS)。但它的输出是 PDF – 而不是网页。
创建 Story 时,会考虑最多三种不同信息源的输入。所有这些项目都是可选的。
HTML 源代码,可以是 Python 字符串,也可以是由脚本创建的,使用 Xml 的方法。
CSS(层叠样式表)源代码,作为 Python 字符串提供。CSS 可用于提供样式信息(文本字体大小、颜色等),就像网页一样。显然,此字符串也可以从文件中读取。
当 DOM 引用图像,或使用除标准 PDF Base 14 字体、CJK 字体以及生成到 PyMuPDF 二进制文件中的 NOTO 字体之外的文本字体时,必须使用 Archive。
The API 允许完全从头创建 DOM,包括所需的样式信息。它也可以用于修改或扩展提供的 HTML:可以删除或替换文本,或更改其样式。也可以添加文本(例如从数据库中提取的文本)并填充模板式的 HTML 文档。
不需要提供语法完整的 HTML 文档:像 <b>Hello
这样的片段完全可以接受,并且许多/大多数语法错误都会自动更正。
在 HTML 被认为完整后,就可以用来创建 PDF 文档。这通过新的 DocumentWriter 类实现。程序员调用其方法创建新的空白页,并将矩形传递给 Story 来填充它们。
story 反过来会返回完成代码,指示是否还有更多内容等待写入。内容的哪一部分将落入哪个矩形或哪个页面由 story 本身自动确定——除了提供矩形之外,无法对其进行影响。
请参阅Stories 示例以了解一些典型用例。
PDF 维护#
PDF 是唯一可以使用 PyMuPDF 进行修改的文档类型。其他文件类型是只读的。
但是,您可以将任何文档(包括图像)转换为 PDF,然后将所有 PyMuPDF 功能应用于转换结果。在此处Document.convert_to_pdf()
了解更多信息,并查看演示脚本 pdf-converter.py,该脚本可以将任何支持的文档转换为 PDF。
Document.save()
始终将 PDF 当前(可能已修改的)状态存储到磁盘上。
通常,您可以选择是保存到新文件,还是只将您的修改附加到现有文件中(“增量保存”),后者通常要快得多。
以下描述了如何操作 PDF 文档的方法。此描述绝非完整:更多内容可在以下章节中找到。
修改、创建、重新排列和删除页面#
有几种方法可以操作 PDF 的所谓页面树(描述所有页面的结构)
Document.delete_page()
和 Document.delete_pages()
用于删除页面。
Document.copy_page()
、Document.fullcopy_page()
和 Document.move_page()
用于在同一文档中复制或移动页面到其他位置。
Document.select()
将 PDF 缩小到选定页面。参数是一个序列 [3],包含您想保留的页码。这些整数都必须在范围 0 <= i < page_count 内。执行后,列表中缺失的所有页面将被删除。剩余页面将按照您指定的顺序和次数 (!) 出现。
因此,您可以轻松创建新的 PDF,包含
前 10 页或后 10 页,
仅奇数页或仅偶数页(用于进行双面打印),
包含或不包含给定文本的页面,
反转页面顺序,……
…… 您能想到的任何操作。
保存的新文档将包含仍然有效的链接、注解和书签(即指向选定页面或某些外部资源)。
Document.insert_page()
和 Document.new_page()
用于插入新页面。
页面本身还可以通过一系列方法进行修改(例如页面旋转、注解和链接维护、文本和图像插入)。
合并和拆分 PDF 文档#
方法 Document.insert_pdf()
用于在不同 PDF 文档之间复制页面。这是一个简单的合并器示例(doc1 和 doc2 是已打开的 PDF)
# append complete doc2 to the end of doc1
doc1.insert_pdf(doc2)
这是一个拆分 doc1 的代码片段。它创建了一个包含其前 10 页和后 10 页的新文档
doc2 = pymupdf.open() # new empty PDF
doc2.insert_pdf(doc1, to_page = 9) # first 10 pages
doc2.insert_pdf(doc1, from_page = len(doc1) - 10) # last 10 pages
doc2.save("first-and-last-10.pdf")
更多信息可在Document章节中找到。也可以看看 PDFjoiner.py。
嵌入数据#
PDF 可以用作任意数据(可执行文件、其他 PDF、文本文件或二进制文件等)的容器,非常类似于 ZIP 压缩包。
PyMuPDF 通过 Document 的 embfile_*
方法和属性完全支持此功能。有关一些详细信息,请阅读附录 3,查阅 Wiki 上关于处理嵌入文件的内容,或示例脚本 embedded-copy.py、embedded-export.py、embedded-import.py 和 embedded-list.py。
保存#
如上所述,Document.save()
将始终以其当前状态保存文档。
您可以通过指定选项 incremental=True
将更改写回原始 PDF。这个过程(通常)非常快,因为更改是附加到原始文件,而无需完全重写。
Document.save()
选项对应于 MuPDF 命令行工具 mutool clean 的选项,请参阅下表。
保存选项 |
mutool |
效果 |
---|---|---|
garbage=1 |
g |
垃圾回收未使用对象 |
garbage=2 |
gg |
除了 1 之外,压缩 |
garbage=3 |
ggg |
除了 2 之外,合并重复对象 |
garbage=4 |
gggg |
除了 3 之外,合并重复流内容 |
clean=True |
cs |
清理和消毒内容流 |
deflate=True |
z |
压缩未压缩流 |
deflate_images=True |
i |
压缩图像流 |
deflate_fonts=True |
f |
压缩字体文件流 |
ascii=True |
a |
将二进制数据转换为 ASCII 格式 |
linear=True |
l |
创建线性化版本 |
expand=True |
d |
解压所有流 |
注意
有关 object
、stream
、xref
等术语的解释,请参阅术语表章节。
例如,mutool clean -ggggz file.pdf
会产生出色的压缩结果。它对应于 doc.save(filename, garbage=4, deflate=True)
。
关闭#
通常希望“关闭”文档,以便在程序继续运行时将底层文件的控制权交还给操作系统。
这可以通过 Document.close()
方法实现。除了关闭底层文件外,与文档关联的缓冲区区域也将被释放。
延伸阅读#
另请参阅 PyMuPDF 的 Wiki 页面。特别是侧边栏标题为“Recipes”下的页面,涵盖了 15 多个以“如何操作”风格编写的主题。
本文档还包含一个常见问题解答。本章与上述 Recipes 密切相关,并将随着时间的推移增加更多内容。
脚注