教程#
本教程将逐步向您展示如何在Python中使用PyMuPDF和MuPDF。
由于 MuPDF 不仅支持 PDF,还支持 XPS、OpenXPS、CBZ、CBR、FB2 和 EPUB 格式,因此 PyMuPDF 也是如此 [1]。然而,出于简洁的考虑,我们将只讨论 PDF 文件。在确实仅支持 PDF 文件的地方,将明确提到这一点。
导入绑定#
通过这个导入语句可以使用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提供支持的渲染引擎。” (引用自维基百科)。
注意
使用遗留名称 fitz
可能会失败,如果安装了无效的 pypi.org 包 fitz
;请参见 安装后的问题。
打开文档#
要访问一个 支持的文档,必须使用以下语句打开:
doc = pymupdf.open(filename) # or pymupdf.Document(filename)
这会创建 Document 对象 doc。 filename 必须是一个 Python 字符串(或一个 pathlib.Path
),指定一个已存在文件的名称。
也可以从内存数据中打开文档,或创建一个新的空PDF。有关详细信息,请参见 Document。您还可以将 Document 用作 上下文管理器。
文档包含许多属性和函数。其中包括元信息(如“作者”或“主题”)、总页数、提纲和加密信息。
一些 文档 方法和属性#
方法 / 属性 |
描述 |
---|---|
页数 (int) |
|
元数据 (dict) |
|
获取目录 (列表) |
|
读取一个 Page |
访问元数据#
PyMuPDF 完全支持标准元数据。 Document.metadata
是一个具有以下键的 Python 字典。它适用于 所有文档类型,虽然并非所有条目都可能始终包含数据。有关其含义和格式的详细信息,请参阅各自的手册,例如 Adobe PDF References 用于 PDF。有关进一步的信息,也可以在章节 Document 中找到。元数据字段是字符串或 None,如果没有其他指示。还要注意,并不是所有字段总是包含有意义的数据 - 即使它们不是 None。
键 |
值 |
---|---|
生产者 |
生产者(生产软件) |
格式 |
格式: ‘PDF-1.4’, ‘EPUB’, 等等。 |
加密 |
如果有,使用的加密方法 |
作者 |
作者 |
modDate |
最后修改日期 |
关键字 |
关键字 |
标题 |
标题 |
creationDate |
创建日期 |
创建者 |
创建应用程序 |
主题 |
主题 |
注意
除了这些标准元数据,PDF 文档 从 PDF 版本 1.4 开始还可能包含所谓的“元数据流”(另见stream
)。此类流中的信息以 XML 编码。PyMuPDF 故意不包含用于此目的的 XML 组件(PyMuPDF Xml 类是一个旨在访问Story对象的 DOM 内容的辅助类),因此我们不直接支持访问包含在其中的信息。但您可以整体提取流,使用像lxml这样的包检查或修改它,然后将结果存回 PDF。如果您愿意,您还可以完全删除这些数据。
注意
在该存储库中有两个实用脚本,分别是元数据导入(仅限PDF)和元数据导出,用于从CSV文件中导入和导出元数据。
处理大纲#
获取文档的所有大纲(也称为“书签”)的最简单方法是加载其目录:
toc = doc.get_toc()
这将返回一个 Python 的列表列表 [[lvl, title, page, …], …],看起来很像书籍中常见的目录。
lvl 是条目的层级级别(从 1 开始),title 是条目的标题,page 是页码(基于 1!)。其他参数描述书签目标的详细信息。
注意
仓库中有两个实用脚本,分别用于 toc import (仅限PDF) 和 toc export 从CSV文件导入和导出目录。
与页面的工作#
页面 处理是 MuPDF 功能的核心。
您可以将页面渲染为位图或矢量(SVG)图像,可以选择进行缩放、旋转、平移或剪切。
您可以以多种格式提取页面的文本和图像,并查找文本字符串。
对于PDF文档,还有更多的方法可用于向页面添加文本或图像。
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)。
A Pixmap 包含若干方法和属性,以下将引用它们。其中包括整数 width、height(均以像素为单位)和 stride(一行水平图像的字节数)。属性 samples 表示一个矩形区域的字节,代表图像数据(一个 Python bytes 对象)。
注意
您还可以通过使用 Page.get_svg_image()
创建页面的 矢量 图像。有关详细信息,请参阅此 矢量图像支持页面。
将页面图像保存到文件中#
我们可以简单地将图像存储在PNG文件中:
pix.save("page-%i.png" % page.number)
在图形用户界面中显示图像#
我们也可以在 GUI 对话框管理器中使用它。 Pixmap.samples
代表一个字节区域,包含所有像素的字节,作为一个 Python 字节对象。这里有一些示例,更多示例请查阅 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 下运行。它需要非常便利的 PySimpleGUI 纯 Python 包。
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”: 文本信息级别与文本版本相同,但包括图像。也可以通过互联网浏览器显示。
“xml”: 不包含图像,但是包含每个单个文本字符的完整位置和字体信息。使用XML模块进行解释。
为了让您了解这些替代方案的输出,我们进行了文本示例提取。请参见附录 1:文本提取的详细信息。
搜索文本#
您可以准确找到某个文本字符串在页面上的具体位置:
areas = page.search_for("mupdf")
这提供了一组矩形(见 Rect),每个矩形包围字符串“mupdf”的一个出现(不区分大小写)。您可以利用这些信息,例如突出显示这些区域(仅限PDF)或创建文档的交叉引用。
请您也查看章节 协同工作:DisplayList 和 TextPage 以及示例程序 demo.py 和 demo-lowlevel.py。它们包含了其他内容,例如 TextPage、Device 和 DisplayList 类如何用于更直接的控制,尤其是在性能问题建议这样做的情况下。
故事:从 HTML 源生成 PDF#
该 Story 类是 PyMuPDF 版本 1.21.0 的新特性。它代表了对 MuPDF 的 “story” 接口的支持。
以下是来自“MuPDF Explored”一书的引用,作者为Robin Watts,出版单位为Artifex:
故事提供了一种方式,以便轻松布局样式化内容,以便与设备一起使用,例如文档撰写器提供的设备(…)。故事的概念源于桌面出版,而桌面出版又从报纸(…)中获取灵感。如果您考虑传统的报纸布局,它将由各种新闻文章(故事)组成,这些文章被布局在多个列中,可能横跨多页。
因此,MuPDF 使用一个故事来表示带有样式信息的文本流。故事的用户可以提供一系列矩形,文本将在这些矩形中布局,然后可以将定位的文本绘制到输出设备上。这使得文本本身(故事)的概念与文本应该流入的区域(布局)分开。
注意
故事的工作方式有点类似于互联网浏览器:它忠实地解析和呈现HTML超文本以及可选的样式表(CSS)。但是它的输出是PDF – 不是网页。
在创建一个 故事 时,会考虑来自多达三个不同信息源的输入。所有这些项目都是可选的。
HTML 源代码,可以是 Python 字符串或 通过脚本创建 使用 Xml 的方法。
CSS(层叠样式表)源代码,作为Python字符串提供。CSS可用于提供样式信息(文本字体大小、颜色等),就像在网页上发生的一样。显然,这个字符串也可以从文件中读取。
每当 DOM 引用图像或使用文本字体(标准 PDF Base 14 Fonts、CJK 字体和生成到 PyMuPDF 二进制文件中的 NOTO 字体除外)时,必须使用 Archive。
该API允许完全从零开始创建DOM,包括所需的样式信息。它还可以用于修改或扩展提供的HTML:文本可以被删除或替换,或其样式可以被更改。文本——例如从数据库提取的——也可以被添加并填充类似模板的HTML文档。
提供语法上完整的HTML文档不是必须的:像 Hello
这样的片段是完全可以接受的,大多数/很多语法错误会自动更正。
在HTML被认为是完整之后,它可以用来创建PDF文档。这是通过新的 DocumentWriter 类实现的。程序员调用它的方法来创建一个新的空白页面,并将矩形传递给Story以填充它们。
这一故事将返回完成代码,指示是否还有更多内容等待写入。内容的哪一部分将落入哪个矩形或哪个页面,都是由故事本身自动决定的——除了提供矩形之外,无法影响这一过程。
请查看Stories recipes以获取多个典型用例。
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")
更多内容可以在文档章节中找到。也可以看看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 |
效果 |
---|---|---|
垃圾=1 |
g |
垃圾收集未使用的对象 |
垃圾=2 |
gg |
除了1之外,紧凑的 |
垃圾=3 |
ggg |
除了2,还合并重复的对象 |
垃圾=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 |
解压所有流 |
注意
有关对象、流、xref等术语的解释,请参阅词汇表章节。
例如, mutool clean -ggggz file.pdf 产生了出色的压缩结果。它对应于 doc.save(filename, garbage=4, deflate=True)。
关闭#
通常希望“关闭”文档,以将底层文件的控制权交给操作系统,同时您的程序继续运行。
这可以通过 Document.close()
方法实现。除了关闭底层文件外,与文档相关的缓冲区区域将被释放。
进一步阅读#
还可以查看 PyMuPDF 的 Wiki 页面。特别是在侧边栏标题“食谱”下列出的那些,涵盖了以“如何做”风格编写的超过 15 个主题。
本文件还包含一个 常见问题解答。本章与上述配方密切相关,并将在未来添加更多内容。
脚注