PDF 按书签拆分听起来简单,但很多人遇到拆分后章节顺序错乱、某些章节丢失、或书签根本没有的问题。今天聊聊纯前端 PDF 按书签拆分的核心问题和解决方案。
PDF 按书签拆分的常见场景
| 场景 | 说明 |
|---|---|
| 分发教材章节 | 每章单独成文件发给学生 |
| 邮件发送单节 | 只发相关章节,不发整本 |
| 按主题归档 | 按章节组织文件而非一个大文件 |
| 减小传输体积 | 小文件更容易分享 |
| 选择性审阅 | 每个同事只看自己负责的章节 |
技术方案:pdf-lib 按书签拆分
核心实现
typescript
import { PDFDocument } from 'pdf-lib'
interface Bookmark {
title: string
page: number
level: number
}
async function splitByBookmarks(
arrayBuffer: ArrayBuffer,
bookmarks: Bookmark[],
maxLevel: number
): Promise<Uint8Array[]> {
const sourceDoc = await PDFDocument.load(arrayBuffer)
const results: Uint8Array[] = []
// 过滤到指定深度,并按页码排序
const relevant = bookmarks
.filter(b => b.level <= maxLevel)
.sort((a, b) => a.page - b.page)
for (let i = 0; i < relevant.length; i++) {
const start = relevant[i].page
// 当前节的结束 = 下一节的开始
const end = i + 1 < relevant.length
? relevant[i + 1].page
: sourceDoc.getPageCount()
if (start >= end) continue
const newDoc = await PDFDocument.create()
const indices = [...range(start, end - 1)]
const copied = await newDoc.copyPages(sourceDoc, indices)
copied.forEach(p => newDoc.addPage(p))
results.push(await newDoc.save())
}
return results
}
关键点:书签指向"起点"而非范围
这是最容易搞错的地方。每个书签只记录它跳转到的起始页,不记录结束页。所以:
- 第 N 节的起点 = 第 N 个书签的 page
- 第 N 节的终点 = 第 N+1 个书签的 page
- 最后一节 → 文档最后一页
arduino
const end = i + 1 < relevant.length
? relevant[i + 1].page // 下一节起点
: sourceDoc.getPageCount() // 最后一节到结尾
关键点:读取书签结构
PDF 的书签存在 /Outlines 入口里,pdf-lib 没有干净的 getOutline() API,需要遍历底层对象树:
ini
const catalog = pdfDoc.catalog
const outlinesRef = catalog.asRawObj().dict.get('Outlines')
每个书签项有 Title(标题)、Dest(跳转目标)、First/Next(嵌套结构指针)。
常见坑位
坑 1:没有书签可拆
扫描件、或重存过的 PDF 往往丢失了大纲结构。
解决方案:
- 拆分前检查 PDF 是否有书签
- 没有书签改用按页码拆分
- 或用编辑器先补书签再拆
坑 2:书签跳转无法解析
某些书签指向已失效的命名目标(named destination),解析不到页码。
解决方案:
- 解析失败时回退到上一个有效的页码
- 在结果里标记该章节
- 拆分后人工核对
坑 3:空章节
两个连续书签指向同一页时,中间没有内容。
解决方案:
- 检测
start >= end的情况跳过 - 避免生成空文件
坑 4:页码从 0 开始
pdf-lib 内部页码是 0 起算,构建复制范围时要小心。
解决方案:
- 统一页码基准
- 复制范围用
[start, end-1] - 拆分后检查每节页数是否正确
坑 5:文件名冲突/非法字符
两个章节可能标题相同,或标题里带 /、: 等非法字符。
解决方案:
- 文件加序号前缀(
01_intro.pdf) - 清洗标题里的非法字符
- 限制文件名长度
按书签拆分 vs 其他拆法
| 方法 | 原理 | 适用 |
|---|---|---|
| 按书签 | 用文档内建大纲 | 有清晰章节结构的文档 |
| 按页码 | 指定起止页 | 已知页码范围 |
| 按大小 | 均分文件大小 | 无书签的大文档 |
| 按空白页 | 在空白页处切分 | 有分隔页的文档 |
| 按页数 | 均分页数 | 均匀分布 |
书签拆分的优势:尊重作者预设的章节边界,不用手动找页码。
使用建议
- 先确认有书签:没有书签就改用按页码拆分
- 选对拆分深度:浅结构选全部,深结构选顶层
- 敏感文档本地处理:不要用上传式工具
- 拆完核对:检查章节顺序和页数
总结
- PDF 按书签拆分核心是 读取大纲 + 计算每节范围
- 有书签的文档效果好,无书签需其他方案
- 纯前端方案适合 私密文档
- 拆分前务必备份原始文件
搜索「PDF按书签拆分」即可找到纯浏览器端工具,文件不上传服务器。