Table of Contents
设置初始视图偏好,而不是无法执行的强制命令
PDF 可以保存页面布局、导航面板和初始目标位置等文档级打开偏好,但不能强迫所有阅读器遵守。浏览器内置查看器、移动应用、无障碍模式、管理员策略、“恢复上次视图”设置和用户选择都可能覆盖或忽略 Catalog 条目。
本文最后核对于 2026 年 9 月 1 日,提供经过验证的 Adobe Acrobat 界面路线和可复现的开源 pikepdf 路线。两者都写入新文件、重新打开并检查 Catalog,而且必须在发布前用真实阅读器测试。
1. PDF Catalog 能控制什么
文档 Catalog 可以包含 /PageLayout 和 /PageMode。Adobe 的 PDF 1.7 参考规范把它们定义为可选的打开偏好。持续维护的 pikepdf 页面布局文档也明确说明同一限制:阅读器可以忽略它们,或允许用户偏好覆盖它们。
/PageLayout 描述页面初次打开时如何排列;它不会合并页面、改变页面框、修改打印,也不会修复逻辑阅读顺序。/PageMode 描述请求打开的导航面板或全屏状态,与页面排列是两个不同设置。
如果值不存在,或图形编辑器保存为“Default”,不要假定所有阅读器表现相同。记录实际 Catalog,并在受众使用的阅读器中执行干净打开测试。
2. PageLayout 的精确名称和含义
这些值是 PDF name object,在文件中带前导斜杠,在代码中表现为 pikepdf.Name 值。
| Catalog 值 | pikepdf 值 | 含义 |
|---|---|---|
/SinglePage | Name.SinglePage | 每次显示一页 |
/OneColumn | Name.OneColumn | 以一个连续栏显示页面 |
/TwoColumnLeft | Name.TwoColumnLeft | 以连续对页栏显示,奇数页在左 |
/TwoColumnRight | Name.TwoColumnRight | 以连续对页栏显示,奇数页在右 |
/TwoPageLeft | Name.TwoPageLeft | 每次显示两页,奇数页在左 |
/TwoPageRight | Name.TwoPageRight | 每次显示两页,奇数页在右 |
“Left”和“Right”影响奇数页摆放的一侧,不会改写页码标签、内容顺序、装订方向或无障碍树。应实际测试封面和第一个跨页,而不是从文件名或语言推断。
3. 经核对的 Adobe Acrobat 界面路线
Adobe 当前的初始视图说明和页面视图文档给出以下路线。编辑文档属性可能需要 Acrobat Pro;标签会随平台、版本和语言变化。
- 制作受保护的备份,在 Acrobat 中打开工作副本。
- Windows 打开菜单并选择 Document properties;macOS 选择 File → Document Properties。
- 选择 Initial View。
- 设置 Page Layout。只有存在已记录的需求时才修改 Navigation Tab、Magnification 或 Open to page。
- 另存为新的输出文件,不要覆盖唯一源文件。
- 完全关闭文档,再重新打开新文件。
- 检查可见结果,并在其他阅读器中测试。
Acrobat 本身也可能在后续打开时恢复上次视图,而不是应用初始偏好。先用新副本或干净配置测试一次,再启用“Restore last view settings when reopening documents”测试一次。“Default”会把布局或缩放交给用户偏好。
4. 可复现的开源环境
下面脚本针对 pikepdf 10.12.0,这是本文审计时所依据的文档版本。使用独立虚拟环境,并让脚本与精确依赖版本一起保存。将来升级前要重新核对版本和 Python 支持范围。
set -euo pipefail
python3 -m venv .venv
. .venv/bin/activate
python -m pip install 'pikepdf==10.12.0'
python -c 'import pikepdf; print(pikepdf.__version__)'
官方 pikepdf 安装指南记录了受支持的 Python/平台组合。Windows 的激活方式不同。不要安装到无关的生产环境;没有授权和批准的秘密处理路线时,不要处理加密或受访问控制的 PDF。
5. 安全的 pikepdf 编辑与验证脚本
将下面内容保存为 set_initial_view.py。它只接受规范定义的名称,拒绝原地写入和覆盖已有输出,创建 input.pdf.bak,只改变请求的 Catalog 字段,保存到不同输出,重新打开结果,检查页数和元数据快照,核对保存的名称,并运行 pikepdf 语法检查。
#!/usr/bin/env python3
from argparse import ArgumentParser
from pathlib import Path
from shutil import copy2
from pikepdf import Name, Pdf
LAYOUTS = {
"SinglePage": Name.SinglePage,
"OneColumn": Name.OneColumn,
"TwoColumnLeft": Name.TwoColumnLeft,
"TwoColumnRight": Name.TwoColumnRight,
"TwoPageLeft": Name.TwoPageLeft,
"TwoPageRight": Name.TwoPageRight,
}
PAGE_MODES = {
"UseNone": Name.UseNone,
"UseOutlines": Name.UseOutlines,
"UseThumbs": Name.UseThumbs,
"FullScreen": Name.FullScreen,
"UseOC": Name.UseOC,
"UseAttachments": Name.UseAttachments,
}
def snapshot(pdf: Pdf) -> tuple[int, dict[str, str], bytes | None]:
docinfo = {str(key): str(value) for key, value in pdf.docinfo.items()}
xmp = (
bytes(pdf.Root.Metadata.read_bytes())
if "/Metadata" in pdf.Root
else None
)
return len(pdf.pages), docinfo, xmp
def main() -> None:
parser = ArgumentParser()
parser.add_argument("input", type=Path)
parser.add_argument("output", type=Path)
parser.add_argument("--layout", required=True, choices=LAYOUTS)
parser.add_argument("--page-mode", choices=PAGE_MODES)
args = parser.parse_args()
source = args.input.resolve()
output = args.output.resolve()
backup = source.with_suffix(source.suffix + ".bak")
if not source.is_file():
parser.error(f"input does not exist: {source}")
if source == output:
parser.error("input and output must be different files")
if output.exists():
parser.error(f"refusing to overwrite output: {output}")
if backup.exists():
parser.error(f"refusing to overwrite backup: {backup}")
copy2(source, backup)
with Pdf.open(source) as pdf:
before = snapshot(pdf)
open_action_present = "/OpenAction" in pdf.Root
pdf.Root.PageLayout = LAYOUTS[args.layout]
if args.page_mode is not None:
pdf.Root.PageMode = PAGE_MODES[args.page_mode]
pdf.save(output, fix_metadata_version=False)
with Pdf.open(output) as check:
after = snapshot(check)
open_action_after = "/OpenAction" in check.Root
actual_layout = str(
check.Root.get("/PageLayout", Name.SinglePage)
)
actual_mode = str(check.Root.get("/PageMode", Name.UseNone))
issues = check.check_pdf_syntax()
if before != after:
raise RuntimeError("page count or metadata changed unexpectedly")
if open_action_after != open_action_present:
raise RuntimeError("OpenAction presence changed unexpectedly")
if actual_layout != f"/{args.layout}":
raise RuntimeError(f"unexpected PageLayout: {actual_layout}")
if args.page_mode is not None and actual_mode != f"/{args.page_mode}":
raise RuntimeError(f"unexpected PageMode: {actual_mode}")
if issues:
raise RuntimeError("PDF syntax issues: " + "; ".join(issues))
print(f"backup: {backup}")
print(f"output: {output}")
print(f"PageLayout: {actual_layout}")
print(f"PageMode: {actual_mode}")
print(f"OpenAction preserved: {open_action_present}")
if __name__ == "__main__":
main()
fix_metadata_version=False 会抑制 pikepdf 自动调整 PDF 版本元数据,但不会让输出字节相同,也不会保留签名。元数据快照是针对 DocumentInfo 和解码后 XMP 的防护,不是合规或隐私审计。
6. 运行、检查并保留证据
在一次性工作副本上运行。基线只改变 /PageLayout;仅当文档有准确、经过验证的大纲,而且阅读器矩阵证明有必要默认打开它时,才添加 --page-mode UseOutlines。
set -euo pipefail
python set_initial_view.py input.pdf output.pdf --layout TwoPageRight
qpdf --check output.pdf
sha256sum input.pdf input.pdf.bak output.pdf
脚本会重新打开输出,并比较 /PageLayout、可选 /PageMode、页数、DocumentInfo 和解码后 XMP。`qpdf --check`提供独立的结构检查。没有 sha256sum 时使用平台等效的 SHA-256 命令。输入与 .bak 哈希相同可证明备份副本一致;输出通常应不同。
把命令、工具版本、哈希、Catalog 值、语法检查结果和阅读器矩阵保存在发布记录中。解析器成功并不能证明页面正确渲染、标签仍符合规范,或所有阅读器都遵守偏好。
7. PageMode 和 OpenAction 的边界
/PageMode 是可选项。谨慎使用,而且只有相应内容真实存在时才设置。
| Catalog 值 | pikepdf 值 | 请求的打开模式 |
|---|---|---|
/UseNone | Name.UseNone | 不显示大纲或缩略图面板 |
/UseOutlines | Name.UseOutlines | 显示文档大纲 |
/UseThumbs | Name.UseThumbs | 显示页面缩略图 |
/FullScreen | Name.FullScreen | 进入全屏模式 |
/UseOC | Name.UseOC | 显示可选内容组面板 |
/UseAttachments | Name.UseAttachments | 显示附件面板 |
普通文档不要默认 /FullScreen:它会移除熟悉的控件,并可能使键盘、移动端和辅助技术用户感到意外。只有书签准确时 /UseOutlines 才有帮助;只有附件符合预期且安全时 /UseAttachments 才有意义。
/OpenAction 是另一个 Catalog 条目。PDF 参考规范允许它是 destination array 或 action dictionary。它可以请求初始页和缩放,但 action 也可能引入 JavaScript、launch、URI 或其他安全敏感行为。基线脚本会保留但不创建、修改或执行 /OpenAction,只报告它是否存在。用可信 PDF 安全工具审查现有 action。不要为了控制布局添加 launch 或 JavaScript action。
8. 在真实阅读器中测试干净打开
每次干净打开测试前,把输出复制为一个新文件名,以免历史记录和缓存掩盖行为。记录精确应用、版本、操作系统、设备和策略状态。
| 阅读器路线 | 首次打开观察 | 覆盖/重开检查 | 无障碍/可用性检查 |
|---|---|---|---|
| Adobe Acrobat/Reader 桌面 | 布局、跨页侧、大纲面板、首页 | 切换恢复上次视图后重开 | 键盘导航、缩放、reflow、屏幕阅读器 |
| Chromium/Edge 内置查看器 | 布局和第一个跨页 | 新配置/隐私窗口和已下载副本 | 浏览器缩放、高对比度、键盘控件 |
| Firefox PDF.js | 布局和第一个跨页 | 干净配置和本地文件打开 | 文本层、键盘控件、屏幕阅读器路线 |
| macOS Preview | 布局与封面位置 | 新副本与最近文档状态比较 | VoiceOver、缩放、侧栏可用性 |
| iOS/Android 指定阅读器 | 竖屏和横屏行为 | 可行时使用新安装/配置 | 动态缩放、触控目标、阅读顺序 |
| 组织无障碍工作流 | Catalog 偏好被采用或忽略 | 批准的阅读器和策略设置 | 标签、标题、替代文本、语言、顺序、表单标签 |
嵌入网页的预览可能使用服务器渲染图片或 JavaScript 查看器,完全忽略 Catalog。这是阅读器结果,不代表 PDF 格式损坏。布局确实重要时,在随附文字中说明推荐视图,同时保留用户改变视图的自由。
9. 无障碍与可用性注意事项
对页在手机和窄窗口中会让文字过小。固定缩放、隐藏界面、强制面板或请求全屏可能妨碍缩放、reflow、导航和退出。应选择温和的布局偏好,并保留用户控制。
初始视图不会创建 PDF 标签,也不会修正逻辑阅读顺序。应独立检查文档标题、语言、各级标题、列表、表格、替代文本、书签、表单标签、链接目的、tab 顺序、对比度和可选文字。对于从右到左或竖排出版物,要用代表性阅读器和辅助技术测试第一个跨页;TwoPageLeft/TwoPageRight 本身不编码语义阅读方向。
不要用页面布局掩盖空白页、错误 crop box 或错误分页;应修复文档结构。
10. 元数据、签名、加密与合规
任何 PDF 编辑器的保存都会重写字节,即使页面内容没变,也可能改变对象顺序、压缩、trailer identifier 或元数据处理。发布前比较描述性元数据和隐私敏感字段。脚本会发现意外的 DocumentInfo/解码后 XMP 变化,但不会清除隐藏元数据。
任何字节变化都可能使现有数字签名失效。pikepdf 的安全文档说明它不支持数字签名。遇到已签名 PDF 就停止,取得获授权的未签名源文件,或在批准的签名步骤之前修改偏好。不要绕过加密或权限,也不要把 PDF 密码写入源码、shell 历史或公开日志。
PDF/A、PDF/UA、PDF/X 和组织规范都需要在保存后分别验证。pikepdf 的元数据文档警告,合规声明不是证明,并建议使用 veraPDF等验证器。重新运行相关验证器和无障碍检查;保留页数和元数据还不够。
11. 回滚与发布检查清单
回滚是撤下 output.pdf,重新发布已验证的 input.pdf.bak 或版本控制中的原件,而不是在唯一源文件上继续“修理”。如果输出已经发布,还要处理 CDN/浏览器缓存,并保存发布时间线和哈希。
- [ ] 源文件获得授权,未签名或安排重新签名,并能通过批准的方式解密。
- [ ] 输入、输出和
.bak相互独立,任何已有文件都不被覆盖。 - [ ] 所选
PageLayout是规范定义的六个名称之一。 - [ ] 除非经过测试的导航需求确有理由,否则省略
PageMode。 - [ ] 已审查现有
OpenAction,没有添加新的 JavaScript 或 launch action。 - [ ] 输出能重新打开,报告预期 Catalog 值和页数。
- [ ] DocumentInfo 和解码后 XMP 一致,隐私元数据另行审查。
- [ ] pikepdf 和 qpdf 结构检查通过,没有忽略错误。
- [ ] 重新执行相关 PDF/A、PDF/UA、PDF/X、签名和无障碍验证。
- [ ] 多阅读器矩阵包含干净打开和用户覆盖/重开测试。
- [ ] 窄屏、键盘、缩放、reflow 和辅助技术仍然可用。
- [ ] 记录发布版本、命令、哈希、观察到的覆盖行为和回滚文件。
12. 当前一手资料
- Adobe Acrobat:设置初始视图
- Adobe Acrobat:调整 PDF 视图
- Adobe:PDF Reference 1.7
- PDF Association:ISO 32000-2 资源
- pikepdf:PDF 查看器默认外观
- pikepdf:安装
- pikepdf:打开与保存
- pikepdf:主 API 与语法检查
- pikepdf:元数据
- pikepdf:安全与签名
- qpdf:命令行检查
- veraPDF:验证器软件
13. 2018 年原文精确存档
以下完整保留 source_export 的可见正文。没有修改任何正文、链接、空格或标点;唯一新增内容是使其失效的外层代码围栏。原始导出和 Git 历史保持不变。
警告:存档中的措辞和 Acrobat 路线是 2018 年历史内容。“force”一词并不构成互操作保证;当前操作应使用上面的维护版指南。
Recently, I read a paper with a default 2-page-in-one-sheet view like a book. Thus, I was wondering how it get achieved.
Open your pad file with Adobe Acrobat or any other editable software. Then, click File->Properties->Initial View->Page Layout->{Any View You Want}
