教程#

本教程将逐步向您展示如何在 Python 中使用 PyMuPDFMuPDF)。

因为 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 的说明#

旧版本的 PyMuPDFPython 导入名称是 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 对象 docfilename 必须是一个 Python 字符串(或 pathlib.Path),指定现有文件的名称。

也可以从内存数据中打开文档,或创建一个新的空白 PDF。详情请参阅 Document。您还可以将 Document 用作上下文管理器

文档包含许多属性和函数。其中包括元信息(如“作者”或“主题”)、总页数、大纲和加密信息。

一些 Document 方法和属性#

方法 / 属性

描述

Document.page_count

页数 (int)

Document.metadata

元数据 (dict)

Document.get_toc()

获取目录 (list)

Document.load_page()

读取一个 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'

一旦您获得了页面对象,以下是您通常会对其执行的操作

渲染页面#

这个示例创建了一个页面的栅格图像

pix = page.get_pixmap()

pix 是一个 Pixmap 对象,它(在本例中)包含页面的 RGB 图像,可用于多种目的。Page.get_pixmap() 方法提供了许多控制图像的变体:分辨率/DPI、色彩空间(例如生成灰度图像或减色方案图像)、透明度、旋转、镜像、平移、倾斜等。例如:要创建 RGBA 图像(即包含 Alpha 通道),请指定 pix = page.get_pixmap(alpha=True)

一个 Pixmap 包含许多方法和属性,如下所示。其中包括整数 widthheight(均以像素为单位)和 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.pydemo-lowlevel.py。它们详细介绍了如何使用 TextPageDeviceDisplayList 类进行更直接的控制,例如在性能考虑建议这样做时。

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 时,会考虑最多三种不同信息源的输入。所有这些项目都是可选的。

  1. HTML 源代码,可以是 Python 字符串,也可以是由脚本创建的,使用 Xml 的方法。

  2. CSS(层叠样式表)源代码,作为 Python 字符串提供。CSS 可用于提供样式信息(文本字体大小、颜色等),就像网页一样。显然,此字符串也可以从文件中读取。

  3. 当 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 文档之间复制页面。这是一个简单的合并器示例(doc1doc2 是已打开的 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 通过 Documentembfile_* 方法和属性完全支持此功能。有关一些详细信息,请阅读附录 3,查阅 Wiki 上关于处理嵌入文件的内容,或示例脚本 embedded-copy.pyembedded-export.pyembedded-import.pyembedded-list.py

保存#

如上所述,Document.save()始终以其当前状态保存文档。

您可以通过指定选项 incremental=True 将更改写回原始 PDF。这个过程(通常)非常快,因为更改是附加到原始文件,而无需完全重写。

Document.save() 选项对应于 MuPDF 命令行工具 mutool clean 的选项,请参阅下表。

保存选项

mutool

效果

garbage=1

g

垃圾回收未使用对象

garbage=2

gg

除了 1 之外,压缩 xref

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

解压所有流

注意

有关 objectstreamxref 等术语的解释,请参阅术语表章节。

例如,mutool clean -ggggz file.pdf 会产生出色的压缩结果。它对应于 doc.save(filename, garbage=4, deflate=True)

关闭#

通常希望“关闭”文档,以便在程序继续运行时将底层文件的控制权交还给操作系统。

这可以通过 Document.close() 方法实现。除了关闭底层文件外,与文档关联的缓冲区区域也将被释放。

延伸阅读#

另请参阅 PyMuPDF 的 Wiki 页面。特别是侧边栏标题为“Recipes”下的页面,涵盖了 15 多个以“如何操作”风格编写的主题。

本文档还包含一个常见问题解答。本章与上述 Recipes 密切相关,并将随着时间的推移增加更多内容。


脚注


本软件按“原样”提供,不提供任何明示或暗示的担保。本软件根据许可协议分发,除该许可协议明确授权外,不得复制、修改或分发。有关许可信息,请访问 artifex.com 或联系 Artifex Software Inc., 39 Mesa Street, Suite 108A, San Francisco CA 94129, United States 获取更多信息。