pypdf 查看器首选项(Viewer Preferences)完全指南:创建、读取与校验
【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf
本文围绕 pypdf 的查看器首选项(Viewer Preferences)功能展开,说明如何在写入 PDF 时通过PdfWriter.create_viewer_preferences()控制阅读器/打印器行为(如隐藏工具栏、双面打印、打印缩放等),以及如何通过PdfReader.viewer_preferences读取已有 PDF 中的设置。读完本文,你将掌握/ViewerPreferences字典的完整属性映射、合法取值与校验规则,并能直接套用可运行的示例代码。
什么是查看器首选项
查看器首选项是 PDF 文档目录(document catalog)中一个可选的/ViewerPreferences字典,用于指定阅读器打开文档时的初始显示与打印行为,其语义定义于 PDF 1.7 规范 第 12.2 节(对应 PDF 2.0 参考手册的 Table 147 等条目)。典型用途包括:
- 控制窗口外观:隐藏工具栏、菜单栏、窗口 UI;
- 控制页面布局:窗口适配、居中、是否显示文档标题;
- 控制非全屏模式、阅读方向;
- 控制打印行为:打印缩放、双面打印、打印页码范围、份数等。
关键前提:/ViewerPreferences字典默认并不存在。写入端必须先调用PdfWriter.create_viewer_preferences()创建它,之后才能通过writer.viewer_preferences属性赋值;读取端如果 PDF 中不存在该字典,PdfReader.viewer_preferences属性会返回None。
完整示例:从零创建一个带查看器首选项的 PDF
以下示例来自官方文档 docs/user/viewer-preferences.md,完整覆盖了 pypdf 支持的全部查看器首选项属性。注意赋值时使用的名称(如/HideToolbar、/UseNone)是 PDF 文件格式层面的名称(Name 对象),以斜杠开头,pypdf 保留这些名称便于开发者对照 PDF 规范检索:
from pypdf import PdfWriter from pypdf.generic import ArrayObject, NumberObject writer = PdfWriter() # 创建 /ViewerPreferences 字典(必须显式调用,字典默认不存在) writer.create_viewer_preferences() # --- 窗口外观:布尔型首选项 --- # /HideToolbar:隐藏阅读器的工具栏 writer.viewer_preferences.hide_toolbar = True # /HideMenubar:隐藏阅读器的菜单栏 writer.viewer_preferences.hide_menubar = True # /HideWindowUI:隐藏窗口中的用户界面元素(如滚动条) writer.viewer_preferences.hide_windowui = True # /FitWindow:打开文档时窗口适配页面 writer.viewer_preferences.fit_window = True # /CenterWindow:打开文档时窗口居中 writer.viewer_preferences.center_window = True # /DisplayDocTitle:窗口标题栏显示文档标题而非文件名 writer.viewer_preferences.display_doctitle = True # --- 非全屏页面模式:Name 型首选项 --- # /NonFullScreenPageMode:定义全屏退出后的页面布局 writer.viewer_preferences.non_fullscreen_pagemode = "/UseNone" # 默认值:无额外面板 writer.viewer_preferences.non_fullscreen_pagemode = "/UseOutlines" # 显示书签/大纲 writer.viewer_preferences.non_fullscreen_pagemode = "/UseThumbs" # 显示缩略图 writer.viewer_preferences.non_fullscreen_pagemode = "/UseOC" # 显示可选内容(图层)面板 # --- 阅读方向:Name 型首选项 --- # /Direction:页面文字阅读顺序 writer.viewer_preferences.direction = "/L2R" # 默认值:从左到右 writer.viewer_preferences.direction = "/R2L" # 从右到左 # --- 页面边界框选择:Name 型首选项 --- # 合法取值均为页面边界框名称:/MediaBox /CropBox /BleedBox /TrimBox /ArtBox # /ViewArea:页面显示区域 writer.viewer_preferences.view_area = "/CropBox" # /ViewClip:页面显示时的裁剪区域 writer.viewer_preferences.view_clip = "/CropBox" # /PrintArea:打印时使用的页面区域 writer.viewer_preferences.print_area = "/CropBox" # /PrintClip:打印时裁剪区域 writer.viewer_preferences.print_clip = "/CropBox" # --- 打印缩放:Name 型首选项 --- # /PrintScaling:打印时的缩放方式 writer.viewer_preferences.print_scaling = "/None" # 禁止缩放 writer.viewer_preferences.print_scaling = "/AppDefault" # 按应用默认值(PDF 规范默认) # --- 双面打印:Name 型首选项 --- # /Duplex:双面打印模式 writer.viewer_preferences.duplex = "/Simplex" # 单面 writer.viewer_preferences.duplex = "/DuplexFlipShortEdge" # 双面,沿短边翻页 writer.viewer_preferences.duplex = "/DuplexFlipLongEdge" # 双面,沿长边翻页 # --- 其余布尔 / 数组 / 整数型首选项 --- # /PickTrayByPDFSize:按页面尺寸选择进纸盒 writer.viewer_preferences.pick_tray_by_pdfsize = True # /PrintPageRange:打印页码范围,须为“首尾成对”的数组 writer.viewer_preferences.print_pagerange = ArrayObject( [NumberObject("1"), NumberObject("10"), NumberObject("20"), NumberObject("30")] ) # /NumCopies:打印份数 writer.viewer_preferences.num_copies = 2 # 添加若干页面并写出 for i in range(40): writer.add_blank_page(10, 10) writer.write("out.pdf")运行后生成的out.pdf打开时,阅读器将隐藏工具栏与菜单栏、窗口适配并居中、显示文档标题,同时携带规定的打印行为(不缩放、双面短边翻页、仅打印 1–10 与 20–30 页、2 份)。
属性与 PDF 键的完整对照表
所有属性定义于 pypdf/generic/_viewerpref.py 的ViewerPreferences类(继承自DictionaryObject)。下表汇总了每个 Python 属性对应的 PDF 字典键、类型及合法取值:
| Python 属性 | PDF 键 | 类型 | 合法取值 / 说明 |
|---|---|---|---|
hide_toolbar | /HideToolbar | bool | 默认False |
hide_menubar | /HideMenubar | bool | 默认False |
hide_windowui | /HideWindowUI | bool | 默认False |
fit_window | /FitWindow | bool | 默认False |
center_window | /CenterWindow | bool | 默认False |
display_doctitle | /DisplayDocTitle | bool | 默认False |
non_fullscreen_pagemode | /NonFullScreenPageMode | Name | /UseNone(默认)、/UseOutlines、/UseThumbs、/UseOC |
direction | /Direction | Name | /L2R(默认)、/R2L |
view_area | /ViewArea | Name | /MediaBox、/CropBox、/BleedBox、/TrimBox、/ArtBox |
view_clip | /ViewClip | Name | 同上(边界框名) |
print_area | /PrintArea | Name | 同上(边界框名) |
print_clip | /PrintClip | Name | 同上(边界框名) |
print_scaling | /PrintScaling | Name | /None、/AppDefault |
duplex | /Duplex | Name | /Simplex、/DuplexFlipShortEdge、/DuplexFlipLongEdge |
pick_tray_by_pdfsize | /PickTrayByPDFSize | bool | 无默认值 |
print_pagerange | /PrintPageRange | Array | 页面“首尾成对”的整数数组,长度必须为偶数 |
num_copies | /NumCopies | int | 非负整数(>= 0) |
enforce | /Enforce | Array | 需要强制执行的查看器首选项名称数组 |
五个边界框名称集中定义在ViewerPreferences模块顶部的BOX_NAMES列表中,其常量值来自 pypdf/constants.py 的PageAttributes(/MediaBox、/CropBox、/BleedBox、/TrimBox、/ArtBox,见 L204–L208)。
取值校验与默认值:源码级说明
ViewerPreferences的属性并非逐一手写,而是在__new__中通过_add_prop_bool、_add_prop_name、_add_prop_arr、_add_prop_int四个工厂函数动态生成的(见 pypdf/generic/_viewerpref.py)。从源码可以确认以下校验规则:
- Name 型属性(
_set_name):值必须以/开头,否则抛出ValueError;若属性声明了合法取值列表且值不在列表中,同样抛出ValueError。例如给non_fullscreen_pagemode赋"toto"或"/toto"都会失败,而print_scaling只接受/None与/AppDefault。 - 数组型属性(
_set_arr):必须传入ArrayObject,否则抛出ValueError;print_pagerange的数组长度必须为偶数(首尾成对),奇数长度会抛出"/PrintPageRange holds page pairs, got N entries"错误;传入None则删除该键。 - 整数型属性(
_set_int):num_copies为负数时抛出ValueError。 - 布尔型默认值:六个窗口外观布尔属性统一使用模块级常量
f_obj = BooleanObject(False)作为默认值(键不存在时读取返回False),而pick_tray_by_pdfsize无默认值(返回None)。
这些规则在 tests/test_writer.py 中有对应测试:test_print_scaling_accepts_the_spec_values(L3617)与test_print_scaling_rejects_other_values(L3626)、test_box_preferences_accept_the_page_boxes(L3640)与test_box_preferences_reject_other_names(L3651)、test_print_pagerange_rejects_an_odd_length(L3661)与test_print_pagerange_accepts_page_pairs(L3673)、test_num_copies_rejects_negative(L3684)与test_num_copies_accepts_zero_and_above(L3694)。
底层实现原理:创建与读取两条路径
写入端:PdfWriter.create_viewer_preferences()定义于 pypdf/_writer.py。它构造一个新的ViewerPreferences()实例,将其作为独立对象注册进 writer(self._add_object(o)),然后挂到文档目录的/ViewerPreferences键(常量CatalogAttributes.VIEWER_PREFERENCES)下,最后返回该实例。因此必须先调用此方法,才能通过writer.viewer_preferences访问并赋值;未调用时直接访问会得到None。测试 tests/test_writer.py 验证了刚创建时字典为空(长度为 0),赋一个值后长度为 1。
读取端:PdfReader.viewer_preferences(定义于 pypdf/_doc_common.py)从文档目录中查找/ViewerPreferences:
- 键不存在时返回
None; - 键存在但指向非字典对象时,记录
logger_warning警告并返回None(对应测试 tests/test_doc_common.py 的“malformed entry”场景); - 键存在且是普通字典时,会将其包装为
ViewerPreferences实例;若该对象带有间接引用,则同步替换 writer/reader 中解析的对象,保证缓存一致性(测试 tests/test_doc_common.py 验证了id(reader.viewer_preferences)多次访问一致)。
create_viewer_preferences()与viewer_preferences读取在PdfWriter与PdfReader上共享同一套属性接口,因此写入后可以立即用PdfReader回读验证。测试test_viewer_preferences__reads_a_well_formed_dictionary(tests/test_doc_common.py)演示了完整的“写入 → 回读”闭环:writer.create_viewer_preferences().center_window = True写出的 PDF,经PdfReader(stream).viewer_preferences读回为{"/CenterWindow": True}。
修改已有 PDF 中的查看器首选项
如果目标 PDF 已包含/ViewerPreferences字典,可以先读取再改写。官方文档给出的路径是:用PdfReader读取原始文件,用PdfWriter(clone_from=reader)克隆,随后通过writer.viewer_preferences直接修改已有值。测试 tests/test_writer.py 展示了这一模式:克隆后读取v.center_window得到原有值,将其改为False后,writer.root_object["/ViewerPreferences"]["/CenterWindow"]立即反映新值;同时print_area == "/CropBox"等原有设置被完整保留。需要注意的是,若原文件不存在该字典,writer.viewer_preferences为None,此时仍需先调用create_viewer_preferences()。
小结
/ViewerPreferences字典默认不存在,写入端必须先调用PdfWriter.create_viewer_preferences();- 读取端
PdfReader.viewer_preferences在字典缺失时返回None; - 所有属性都带有严格的取值校验:Name 值必须以
/开头并在白名单内,print_pagerange必须为偶数长度的ArrayObject,num_copies必须非负; - 五个边界框属性(
view_area、view_clip、print_area、print_clip)只接受BOX_NAMES中的五种页面边界框名称; - 布尔型窗口首选项的默认值均为
False,non_fullscreen_pagemode与direction的默认值分别为/UseNone与/L2R。
对照 PDF 规范查阅这些以斜杠开头的名称时,可直接在 pypdf 文档中搜索/HideToolbar、/PrintScaling、/Duplex等键名,属性和 PDF 键一一对应,便于快速定位。
【免费下载链接】pypdfA pure-python PDF library capable of splitting, merging, cropping, and transforming the pages of PDF files项目地址: https://gitcode.com/GitHub_Trending/py/pypdf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考