可靠设置 PDF 初始视图:Acrobat 与 pikepdf 指南

设置初始视图偏好,而不是无法执行的强制命令

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 值含义
/SinglePageName.SinglePage每次显示一页
/OneColumnName.OneColumn以一个连续栏显示页面
/TwoColumnLeftName.TwoColumnLeft以连续对页栏显示,奇数页在左
/TwoColumnRightName.TwoColumnRight以连续对页栏显示,奇数页在右
/TwoPageLeftName.TwoPageLeft每次显示两页,奇数页在左
/TwoPageRightName.TwoPageRight每次显示两页,奇数页在右

“Left”和“Right”影响奇数页摆放的一侧,不会改写页码标签、内容顺序、装订方向或无障碍树。应实际测试封面和第一个跨页,而不是从文件名或语言推断。

3. 经核对的 Adobe Acrobat 界面路线

Adobe 当前的初始视图说明页面视图文档给出以下路线。编辑文档属性可能需要 Acrobat Pro;标签会随平台、版本和语言变化。

  1. 制作受保护的备份,在 Acrobat 中打开工作副本。
  2. Windows 打开菜单并选择 Document properties;macOS 选择 File → Document Properties
  3. 选择 Initial View
  4. 设置 Page Layout。只有存在已记录的需求时才修改 Navigation TabMagnificationOpen to page
  5. 另存为新的输出文件,不要覆盖唯一源文件。
  6. 完全关闭文档,再重新打开新文件。
  7. 检查可见结果,并在其他阅读器中测试。

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 值请求的打开模式
/UseNoneName.UseNone不显示大纲或缩略图面板
/UseOutlinesName.UseOutlines显示文档大纲
/UseThumbsName.UseThumbs显示页面缩略图
/FullScreenName.FullScreen进入全屏模式
/UseOCName.UseOC显示可选内容组面板
/UseAttachmentsName.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 顺序、对比度和可选文字。对于从右到左或竖排出版物,要用代表性阅读器和辅助技术测试第一个跨页;TwoPageLeftTwoPageRight 本身不编码语义阅读方向。

不要用页面布局掩盖空白页、错误 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. 当前一手资料

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}

Leave a Reply