PyMuPDF 1.24.4 中文文档(五)(2)https://developer.aliyun.com/article/1559437
低级接口
原文:
pymupdf.readthedocs.io/en/latest/recipes-low-level-interfaces.html
有许多方法可以在相对低级别上访问和操作 PDF 文件。诚然,“低级别”和“常规”功能之间的明确区分并非总是可能的,也是主观品味的问题。
也可能发生之前被认为是低级的功能后来被评估为是正常接口的一部分。在 v1.14.0 版本中,Tools 类已经发生了这种情况 —— 现在它作为“类”章节中的一个条目找到。
只需查找文档的哪个章节,就能找到所需内容,这仅仅是文档问题。所有内容都可以通过同一界面随时获取。
如何迭代通过xref
表
PDF 的xref
表是文件中定义的所有对象的列表。该表可能包含许多条目 — 例如,Adobe PDF 参考手册中有 127,000 个对象。表条目“0”被保留,不得触及。以下脚本循环遍历xref
表,并打印每个对象的定义:
>>> xreflen = doc.xref_length() # length of objects table >>> for xref in range(1, xreflen): # skip item 0! print("") print("object %i (stream: %s)" % (xref, doc.xref_is_stream(xref))) print(doc.xref_object(xref, compressed=False))
以下内容将被生成:
object 1 (stream: False) << /ModDate (D:20170314122233-04'00') /PXCViewerInfo (PDF-XChange Viewer;2.5.312.1;Feb 9 2015;12:00:06;D:20170314122233-04'00') >> object 2 (stream: False) << /Type /Catalog /Pages 3 0 R >> object 3 (stream: False) << /Kids [ 4 0 R 5 0 R ] /Type /Pages /Count 2 >> object 4 (stream: False) << /Type /Page /Annots [ 6 0 R ] /Parent 3 0 R /Contents 7 0 R /MediaBox [ 0 0 595 842 ] /Resources 8 0 R >> ... object 7 (stream: True) << /Length 494 /Filter /FlateDecode >> ...
PDF 对象定义是一个普通的 ASCII 字符串。
如何处理对象流
除对象定义外,某些对象类型还包含额外数据。例如,图片、字体、嵌入文件或描述页面外观的命令。
这些类型的对象称为“流对象”。PyMuPDF 允许通过方法Document.xref_stream()
读取对象的流,参数为对象的xref
。还可以使用Document.update_stream()
写回流的修改版本。
假设以下代码片段想要读取 PDF 的所有流,无论出于何种原因:
>>> xreflen = doc.xref_length() # number of objects in file >>> for xref in range(1, xreflen): # skip item 0! if stream := doc.xref_stream(xref): # do something with it (it is a bytes object or None) # e.g. just write it back: doc.update_stream(xref, stream)
Document.xref_stream()
自动以字节对象形式返回解压的流 — 而Document.update_stream()
如果有益,也会自动压缩它。
如何处理页面内容
PDF 页面可以有零个或多个contents
对象。这些是描述页面上出现的内容及其方式(如文本和图像)的流对象。它们以特殊的迷你语言编写,如 Adobe PDF 参考手册中第 643 页的附录 A“操作符概要”中所述。
每个 PDF 阅读应用程序必须能够解释内容语法,以重现页面的预期外观。
如果提供了多个contents
对象,则必须按照指定的顺序解释它们,就像它们是按顺序串联起来的一样。
对于拥有多个contents
对象存在一些技术上的优点:
- 仅仅添加新的
contents
对象要比维护单个大对象更加简单和快速(后者涉及每次更改都需要读取、解压缩、修改、重新压缩和重写)。 - 当使用增量更新时,修改过的大型
contents
对象会增加更新的增量,因此很容易抵消增量保存的效率。
例如,PyMuPDF 在Page.insert_image()
、Page.show_pdf_page()
方法以及 Shape 方法中添加新的小型contents
对象。
然而,也有一些情况下单个contents
对象更有利:它比多个较小的对象更容易解释和更易于压缩。
这里有两种合并页面多个内容的方法:
>>> # method 1: use the MuPDF clean function >>> page.clean_contents() # cleans and combines multiple Contents >>> xref = page.get_contents()[0] # only one /Contents now! >>> cont = doc.xref_stream(xref) >>> # this has also reformatted the PDF commands >>> # method 2: extract concatenated contents >>> cont = page.read_contents() >>> # the /Contents source itself is unmodified
清理函数Page.clean_contents()
不仅仅是粘合contents
对象:它还会更正和优化页面的 PDF 操作符语法,并消除与页面对象定义的任何不一致之处。
如何访问 PDF 目录
这是 PDF 的中心(“根”)对象。它作为到达重要其他对象的起点,并包含 PDF 的一些全局选项:
>>> import pymupdf >>> doc=pymupdf.open("PyMuPDF.pdf") >>> cat = doc.pdf_catalog() # get xref of the /Catalog >>> print(doc.xref_object(cat)) # print object definition << /Type/Catalog % object type /Pages 3593 0 R % points to page tree /OpenAction 225 0 R % action to perform on open /Names 3832 0 R % points to global names tree /PageMode /UseOutlines % initially show the TOC /PageLabels<</Nums[0<</S/D>>2<</S/r>>8<</S/D>>]>> % labels given to pages /Outlines 3835 0 R % points to outline tree >>
注意
缩进、换行和注释仅用于澄清目的,通常不会显示。有关 PDF 目录的更多信息,请参见 Adobe PDF References 第 71 页的第 7.7.2 节。
如何访问 PDF 文件尾页
PDF 文件的尾页是位于文件末尾的dictionary
。它包含特殊对象和指向重要其他信息的指针。参见 Adobe PDF References 第 42 页。以下是概述:
Key | Type | Value |
Size | int | 交叉引用表中条目数 + 1。 |
Prev | int | 指向前一个xref 部分的偏移量(指示增量更新)。 |
Root | dictionary | (间接)指向目录的指针。请参阅上一节。 |
Encrypt | dictionary | 指向加密对象的指针(仅加密文件)。 |
Info | dictionary | (间接)指向信息(元数据)的指针。 |
ID | array | 由两个字节字符串组成的文件标识符。 |
XRefStm | int | 交叉引用流的偏移量。参见 Adobe PDF References 第 49 页。 |
通过 PyMuPDF 的Document.pdf_trailer()
或者等效地通过Document.xref_object()
使用-1 而不是有效的xref
编号来访问这些信息。
>>> import pymupdf >>> doc=pymupdf.open("PyMuPDF.pdf") >>> print(doc.xref_object(-1)) # or: print(doc.pdf_trailer()) << /Type /XRef /Index [ 0 8263 ] /Size 8263 /W [ 1 3 1 ] /Root 8260 0 R /Info 8261 0 R /ID [ <4339B9CEE46C2CD28A79EBDDD67CC9B3> <4339B9CEE46C2CD28A79EBDDD67CC9B3> ] /Length 19883 /Filter /FlateDecode >> >>>
如何访问 XML 元数据
PDF 可能包含标准元数据格式之外的 XML 元数据。事实上,大多数 PDF 查看器或修改软件在保存 PDF 时添加此类信息(Adobe、Nitro PDF、PDF-XChange 等)。
PyMuPDF 无法直接解释或更改此信息,因为它不包含 XML 功能。但是,XML 元数据存储为stream
对象,因此可以使用适当的软件进行读取、修改和写回。
>>> xmlmetadata = doc.get_xml_metadata() >>> print(xmlmetadata) <?xpacket begin="\ufeff" id="W5M0MpCehiHzreSzNTczkc9d"?> <x:xmpmeta x:xmptk="3.1-702"> <rdf:RDF > ... omitted data ... <?xpacket end="w"?>
使用某些 XML 包,可以解释和/或修改 XML 数据,然后存储回去。如果 PDF 以前没有 XML 元数据,则以下方法也适用:
>>> # write back modified XML metadata: >>> doc.set_xml_metadata(xmlmetadata) >>> >>> # XML metadata can be deleted like this: >>> doc.del_xml_metadata()
如何扩展 PDF 元数据
属性Document.metadata
的设计适用于所有支持的文档类型,工作方式相同:它是一个 Python 字典,具有固定的键值对集合。对应地,Document.set_metadata()
仅接受标准键。
然而,PDF 可能包含无法像这样访问的项目。此外,可能存在存储额外信息的原因,如版权信息。以下是使用 PyMuPDF 低级函数处理任意元数据项的方法。
例如,看一看某些 PDF 的标准元数据输出:
# --------------------- # standard metadata # --------------------- pprint(doc.metadata) {'author': 'PRINCE', 'creationDate': "D:2010102417034406'-30'", 'creator': 'PrimoPDF http://www.primopdf.com/', 'encryption': None, 'format': 'PDF 1.4', 'keywords': '', 'modDate': "D:20200725062431-04'00'", 'producer': 'macOS Version 10.15.6 (Build 19G71a) Quartz PDFContext, ' 'AppendMode 1.1', 'subject': '', 'title': 'Full page fax print', 'trapped': ''}
使用以下代码查看存储在元数据对象中的所有项:
# ---------------------------------- # metadata including private items # ---------------------------------- metadata = {} # make my own metadata dict what, value = doc.xref_get_key(-1, "Info") # /Info key in the trailer if what != "xref": pass # PDF has no metadata else: xref = int(value.replace("0 R", "")) # extract the metadata xref for key in doc.xref_get_keys(xref): metadata[key] = doc.xref_get_key(xref, key)[1] pprint(metadata) {'Author': 'PRINCE', 'CreationDate': "D:2010102417034406'-30'", 'Creator': 'PrimoPDF http://www.primopdf.com/', 'ModDate': "D:20200725062431-04'00'", 'PXCViewerInfo': 'PDF-XChange Viewer;2.5.312.1;Feb 9 ' "2015;12:00:06;D:20200725062431-04'00'", 'Producer': 'macOS Version 10.15.6 (Build 19G71a) Quartz PDFContext, ' 'AppendMode 1.1', 'Title': 'Full page fax print'} # --------------------------------------------------------------- # note the additional 'PXCViewerInfo' key - ignored in standard! # ---------------------------------------------------------------
反之亦然,您还可以在 PDF 中存储私有元数据项。您有责任确保这些项符合 PDF 规范 - 特别是它们必须是(unicode)字符串。请参阅 Adobe PDF References 第 14.3 节(第 548 页)获取详细信息和注意事项:
what, value = doc.xref_get_key(-1, "Info") # /Info key in the trailer if what != "xref": raise ValueError("PDF has no metadata") xref = int(value.replace("0 R", "")) # extract the metadata xref # add some private information doc.xref_set_key(xref, "mykey", pymupdf.get_pdf_str("北京 is Beijing")) # # after executing the previous code snippet, we will see this: pprint(metadata) {'Author': 'PRINCE', 'CreationDate': "D:2010102417034406'-30'", 'Creator': 'PrimoPDF http://www.primopdf.com/', 'ModDate': "D:20200725062431-04'00'", 'PXCViewerInfo': 'PDF-XChange Viewer;2.5.312.1;Feb 9 ' "2015;12:00:06;D:20200725062431-04'00'", 'Producer': 'macOS Version 10.15.6 (Build 19G71a) Quartz PDFContext, ' 'AppendMode 1.1', 'Title': 'Full page fax print', 'mykey': '北京 is Beijing'}
要删除选定的键,请使用doc.xref_set_key(xref, "mykey", "null")
。如下一节所述,字符串“null”是 Python 的None
的 PDF 等效项。具有该值的键将被视为未指定 - 并在垃圾收集中物理删除。
如何读取和更新 PDF 对象
还存在一些精细的、优雅的方法来访问和操作选择的 PDF字典
键。
Document.xref_get_keys()
返回xref
处对象的 PDF 键:
In [1]: import pymupdf In [2]: doc = pymupdf.open("pymupdf.pdf") In [3]: page = doc[0] In [4]: from pprint import pprint In [5]: pprint(doc.xref_get_keys(page.xref)) ('Type', 'Contents', 'Resources', 'MediaBox', 'Parent')
- 与完整对象定义进行比较:
In [6]: print(doc.xref_object(page.xref)) << /Type /Page /Contents 1297 0 R /Resources 1296 0 R /MediaBox [ 0 0 612 792 ] /Parent 1301 0 R >>
- 单个键也可以直接访问,通过
Document.xref_get_key()
。该值总是一个字符串,并附带类型信息,有助于解释它:
In [7]: doc.xref_get_key(page.xref, "MediaBox") Out[7]: ('array', '[0 0 612 792]')
- 这里是上述页面键的完整列表:
In [9]: for key in doc.xref_get_keys(page.xref): ...: print("%s = %s" % (key, doc.xref_get_key(page.xref, key))) ...: Type = ('name', '/Page') Contents = ('xref', '1297 0 R') Resources = ('xref', '1296 0 R') MediaBox = ('array', '[0 0 612 792]') Parent = ('xref', '1301 0 R')
- 未定义的键查询返回
('null', 'null')
– PDF 对象类型null
对应于 Python 中的None
。布尔值true
和false
类似。 - 让我们向页面定义添加一个新键,将其旋转角度设置为 90 度(您知道实际上已经存在
Page.set_rotation()
吗?):
In [11]: doc.xref_get_key(page.xref, "Rotate") # no rotation set: Out[11]: ('null', 'null') In [12]: doc.xref_set_key(page.xref, "Rotate", "90") # insert a new key In [13]: print(doc.xref_object(page.xref)) # confirm success << /Type /Page /Contents 1297 0 R /Resources 1296 0 R /MediaBox [ 0 0 612 792 ] /Parent 1301 0 R /Rotate 90 >>
- 通过将其值设置为
null
,此方法还可用于从xref
字典中删除一个键:以下内容将从页面中删除旋转规范:doc.xref_set_key(page.xref, "Rotate", "null")
。同样地,要从页面中删除所有链接、注释和字段,请使用doc.xref_set_key(page.xref, "Annots", "null")
。因为Annots
的定义是一个数组,因此在这种情况下,通过语句doc.xref_set_key(page.xref, "Annots", "[]")
将执行相同的操作。 - PDF 字典可以按层次嵌套。在以下页面对象定义中,Font 和
XObject
都是Resources
的子字典:
In [15]: print(doc.xref_object(page.xref)) << /Type /Page /Contents 1297 0 R /Resources << /XObject << /Im1 1291 0 R >> /Font << /F39 1299 0 R /F40 1300 0 R >> >> /MediaBox [ 0 0 612 792 ] /Parent 1301 0 R /Rotate 90 >>
- 上述情况得到了支持,通过方法
Document.xref_set_key()
和Document.xref_get_key()
:使用类似路径的符号指向所需的键。例如,要检索上述键Im1
的值,需在键参数中指定完整的字典链:"Resources/XObject/Im1"
:
In [16]: doc.xref_get_key(page.xref, "Resources/XObject/Im1") Out[16]: ('xref', '1291 0 R')
- 路径符号也可以用来直接设置一个值:使用以下内容让
Im1
指向一个不同的对象:
In [17]: doc.xref_set_key(page.xref, "Resources/XObject/Im1", "9999 0 R") In [18]: print(doc.xref_object(page.xref)) # confirm success: << /Type /Page /Contents 1297 0 R /Resources << /XObject << /Im1 9999 0 R >> /Font << /F39 1299 0 R /F40 1300 0 R >> >> /MediaBox [ 0 0 612 792 ] /Parent 1301 0 R /Rotate 90 >>
- 注意,这里完全没有语义检查:如果 PDF 没有 xref 9999,这一点将不会在此时被检测到。
- 如果键不存在,通过设置其值将创建它。此外,如果任何中间键也不存在,它们也将根据需要被创建。以下示例创建了一个在现有字典
A
几个级别下的数组D
:
In [5]: print(doc.xref_object(xref)) # some existing PDF object: << /A << >> >> In [6]: # the following will create 'B', 'C' and 'D' In [7]: doc.xref_set_key(xref, "A/B/C/D", "[1 2 3 4]") In [8]: print(doc.xref_object(xref)) # check out what happened: << /A << /B << /C << /D [ 1 2 3 4 ] >> >> >> >>
- 在设置键值时,MuPDF 将执行基本的PDF 语法检查。例如,新键只能创建在字典的下面。以下尝试在先前创建的数组
D
下创建一些新的字符串项E
:
In [9]: # 'D' is an array, no dictionary! In [10]: doc.xref_set_key(xref, "A/B/C/D/E", "(hello)") mupdf: not a dict (array) --- ... --- RuntimeError: not a dict (array)
- 也不可能创建一个键,如果某些更高级别的键是一个**“间接”**对象,即一个 xref。换句话说,xrefs 只能直接修改,而不能通过引用它们的其他对象隐式地修改它们:
In [13]: # the following object points to an xref In [14]: print(doc.xref_object(4)) << /E 3 0 R >> In [15]: # 'E' is an indirect object and cannot be modified here! In [16]: doc.xref_set_key(4, "E/F", "90") mupdf: path to 'F' has indirects --- ... --- RuntimeError: path to 'F' has indirects
注意
这些都是专家级功能!并没有验证是否指定了有效的 PDF 对象、xrefs 等。与其他低级方法一样,渲染 PDF 或其中的部分无法使用的风险存在。
对此页面有任何反馈吗?
本软件按原样提供,不附带任何明示或暗示的保证。此软件根据许可分发,未经许可明确授权,不得复制、修改或分发。有关更多信息,请参阅artifex.com上的许可信息或联系 Artifex Software Inc.,39 Mesa Street,Suite 108A,San Francisco CA 94129,United States。
此文档涵盖了所有版本直至 1.24.4。
如何遍历xref
表
PDF 的xref
表是文件中定义的所有对象的列表。此表可能很容易包含许多条目 - 例如,手册 Adobe PDF References 有 127,000 个对象。表条目“0”已保留,不得触摸。以下脚本循环遍历xref
表并打印每个对象的定义:
>>> xreflen = doc.xref_length() # length of objects table >>> for xref in range(1, xreflen): # skip item 0! print("") print("object %i (stream: %s)" % (xref, doc.xref_is_stream(xref))) print(doc.xref_object(xref, compressed=False))
这将产生以下输出:
object 1 (stream: False) << /ModDate (D:20170314122233-04'00') /PXCViewerInfo (PDF-XChange Viewer;2.5.312.1;Feb 9 2015;12:00:06;D:20170314122233-04'00') >> object 2 (stream: False) << /Type /Catalog /Pages 3 0 R >> object 3 (stream: False) << /Kids [ 4 0 R 5 0 R ] /Type /Pages /Count 2 >> object 4 (stream: False) << /Type /Page /Annots [ 6 0 R ] /Parent 3 0 R /Contents 7 0 R /MediaBox [ 0 0 595 842 ] /Resources 8 0 R >> ... object 7 (stream: True) << /Length 494 /Filter /FlateDecode >> ...
PDF 对象定义是普通的 ASCII 字符串。
如何处理对象流
一些对象类型除了其对象定义之外还包含其他数据。例如,图像、字体、嵌入文件或描述页面外观的命令。
这些类型的对象称为“流对象”。PyMuPDF 允许通过方法Document.xref_stream()
读取对象的流,其中对象的xref
作为参数。还可以使用Document.update_stream()
写回修改后的流的版本。
假设以下代码片段想要出于任何原因读取 PDF 的所有流:
>>> xreflen = doc.xref_length() # number of objects in file >>> for xref in range(1, xreflen): # skip item 0! if stream := doc.xref_stream(xref): # do something with it (it is a bytes object or None) # e.g. just write it back: doc.update_stream(xref, stream)
Document.xref_stream()
自动返回解压缩为字节对象的流 - 如果有利,Document.update_stream()
会自动压缩它。
如何处理页面内容
PDF 页面可以有零个或多个contents
对象。这些是描述页面上出现的内容的流对象(如文本和图像)。它们是用特殊的迷你语言编写的,例如在 Adobe PDF References 的第 643 页的第“附录 A - 运算符摘要”中描述。
每个 PDF 阅读器应用程序必须能够解释内容语法,以重现页面的预期外观。
如果提供了多个contents
对象,则必须按照指定顺序解释它们,就像它们是几个对象的串联一样。
有多个contents
对象的技术优势:
- 添加新的
contents
对象比维护一个单一的大对象要简单得多,速度也更快(因为每次更改都需要读取、解压缩、修改、重新压缩和重写)。 - 在处理增量更新时,修改过的大
contents
对象会使更新增量膨胀,因此很容易抵消增量保存的效率。
例如,PyMuPDF 在Page.insert_image()
,Page.show_pdf_page()
方法和 Shape 方法中添加了新的小contents
对象。
然而,在某些情况下,单个contents
对象也是有利的:它比多个较小的对象更易于解释,且更易于压缩。
以下是组合页面多个内容的两种方法:
>>> # method 1: use the MuPDF clean function >>> page.clean_contents() # cleans and combines multiple Contents >>> xref = page.get_contents()[0] # only one /Contents now! >>> cont = doc.xref_stream(xref) >>> # this has also reformatted the PDF commands >>> # method 2: extract concatenated contents >>> cont = page.read_contents() >>> # the /Contents source itself is unmodified
清理函数Page.clean_contents()
不仅粘合contents
对象,还会修正和优化页面的 PDF 操作符语法,并删除页面对象定义的任何不一致之处。
如何访问 PDF 目录
这是 PDF 的中心(“根”)对象。它作为到达重要其他对象的起点,并且还包含 PDF 的一些全局选项:
>>> import pymupdf >>> doc=pymupdf.open("PyMuPDF.pdf") >>> cat = doc.pdf_catalog() # get xref of the /Catalog >>> print(doc.xref_object(cat)) # print object definition << /Type/Catalog % object type /Pages 3593 0 R % points to page tree /OpenAction 225 0 R % action to perform on open /Names 3832 0 R % points to global names tree /PageMode /UseOutlines % initially show the TOC /PageLabels<</Nums[0<</S/D>>2<</S/r>>8<</S/D>>]>> % labels given to pages /Outlines 3835 0 R % points to outline tree >>
注意
缩进、换行和注释仅为澄清目的而插入,通常不会显示。有关 PDF 目录的更多信息,请参见 Adobe PDF References 第 71 页的 7.7.2 节。
PyMuPDF 1.24.4 中文文档(五)(4)https://developer.aliyun.com/article/1559439