Calibre 的 --pdf-add-toc 会把目录页插在 PDF 末尾,方便打印但不适合电子阅读。EPUB 自带的目录书签完整,但同样落在尾部。

--use-auto-toc 用 XPath 自动生成目录,效果不稳定,经常只匹配到少量标题。

方案 链接到标题

两步处理:

  1. Calibre 转换(不加 --pdf-add-toc,保留 EPUB 自带目录)
  2. PyMuPDF 脚本把末尾目录页移到正文前方
ebook-convert input.epub output.pdf

生成的文件目录页在最后两页,用以下脚本移动到第 3 页之后:

import fitz

src = '某书_with_toc.pdf'
dst = '某书_with_toc_moved.pdf'
doc = fitz.open(src)
total = len(doc)
toc_start = total - 2  # 目录页起始位置

# 页面映射:旧页码 → 新页码
old_to_new = {}
idx = 0
for old in range(3):           # 前3页:空白、扉页、格言页
    old_to_new[old] = idx; idx += 1
for old in range(toc_start, total):  # 目录页
    old_to_new[old] = idx; idx += 1
for old in range(3, toc_start):      # 正文
    old_to_new[old] = idx; idx += 1

new_doc = fitz.open()
new_doc.insert_pdf(doc, 0, 2)              # 前3页
new_doc.insert_pdf(doc, toc_start, total-1)  # 目录页
new_doc.insert_pdf(doc, 3, toc_start-1)     # 正文

# 修正书签页码
toc = doc.get_toc()
new_toc = [[lvl, title, old_to_new[page-1]+1] for lvl, title, page in toc]
new_doc.set_toc(new_toc)

new_doc.save(dst)

结果 链接到标题

  • 目录页位于扉页/格言页之后,正文之前
  • 书签数量不变,页码自动修正,点击跳转正确

注意事项 链接到标题

  • --use-auto-toc--level1-toc="//h:h1" 等 XPath 对 EPUB 内部结构依赖大,优先用自带目录
  • set_toc 需在 insert_pdf 完成后调用,页码为 1-based
  • 如果目录页不在末尾,需先确认 toc_start 位置(通过 get_toc() 或观察页内容)