pypdf 查看器首选项(Viewer Preferences)完全指南:创建、读取与校验
2026/9/15 20:33:48 网站建设 项目流程

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/HideToolbarbool默认False
hide_menubar/HideMenubarbool默认False
hide_windowui/HideWindowUIbool默认False
fit_window/FitWindowbool默认False
center_window/CenterWindowbool默认False
display_doctitle/DisplayDocTitlebool默认False
non_fullscreen_pagemode/NonFullScreenPageModeName/UseNone(默认)、/UseOutlines/UseThumbs/UseOC
direction/DirectionName/L2R(默认)、/R2L
view_area/ViewAreaName/MediaBox/CropBox/BleedBox/TrimBox/ArtBox
view_clip/ViewClipName同上(边界框名)
print_area/PrintAreaName同上(边界框名)
print_clip/PrintClipName同上(边界框名)
print_scaling/PrintScalingName/None/AppDefault
duplex/DuplexName/Simplex/DuplexFlipShortEdge/DuplexFlipLongEdge
pick_tray_by_pdfsize/PickTrayByPDFSizebool无默认值
print_pagerange/PrintPageRangeArray页面“首尾成对”的整数数组,长度必须为偶数
num_copies/NumCopiesint非负整数(>= 0
enforce/EnforceArray需要强制执行的查看器首选项名称数组

五个边界框名称集中定义在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,否则抛出ValueErrorprint_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读取在PdfWriterPdfReader上共享同一套属性接口,因此写入后可以立即用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_preferencesNone,此时仍需先调用create_viewer_preferences()

小结

  • /ViewerPreferences字典默认不存在,写入端必须先调用PdfWriter.create_viewer_preferences()
  • 读取端PdfReader.viewer_preferences在字典缺失时返回None
  • 所有属性都带有严格的取值校验:Name 值必须以/开头并在白名单内,print_pagerange必须为偶数长度的ArrayObjectnum_copies必须非负;
  • 五个边界框属性(view_areaview_clipprint_areaprint_clip)只接受BOX_NAMES中的五种页面边界框名称;
  • 布尔型窗口首选项的默认值均为Falsenon_fullscreen_pagemodedirection的默认值分别为/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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询