Structure
Structure
本页介绍文档导航和逻辑结构:大纲(书签)、目录、命名目标,以及用于可访问性和重排的标记结构树。
大纲(书签)
Document.GetOutlines() 返回当前的大纲树,作为 OutlineItem[](当不存在 /Outlines 时为 []);Document.SetOutlines() 替换它 — 传入 [] 会完全删除大纲。每个 OutlineItem 都有一个 Title、一个 Dest(页码或命名目标),可选的用于嵌套的 Children,以及样式(Color、Bold、Italic、Open)。
const items: OutlineItem[] = sections.map((s) => {
const item: OutlineItem = { Title: s.title, Dest: { name: s.dest } };
if (s.subtype === 'sales') {
item.Open = true;
item.Children = ['Pasta', 'Pizza', 'Antipasti']
.map((cat) => ({ Title: cat, Dest: { name: s.dest } }));
}
return item;
});
doc.SetOutlines(items);目录
Page.AddTOC() 将一系列 TOCEntry 对象渲染为页面上的一个矩形——每个对象都有一个 title 和一个目标 page 编号——并带有换行的标题、点线引导以及右对齐的页码标签。它返回一个 AddTOCResult,报告绘制了多少条目;如果盒子太小,则返回未能容纳的 remainder。
page.AddTOC(
sections.map((s, i) => ({ title: `${i + 1}. ${s.title}`, page: s.page.Number })),
[72, 160, 400, 400],
{ font: 'Helvetica', fontSize: 13, rowGap: 18 },
);命名目标
Document.GetNamedDestinations() 返回每个已命名的目标——合并 /Names /Dests 名称树和传统的 /Dests 字典——以 { name, dest } 对的形式,其中 dest 是一个 PageDest({ page, view })。这些与 OutlineItem.Dest 或链接注释可以通过名称而不是显式页码引用的已命名目标相同。
const destNames = new Set(doc.GetNamedDestinations().map((d) => d.name));
for (const { name, dest } of doc.GetNamedDestinations()) {
console.log(name, '-> page', dest.page);
}已标记结构树
Document.AutoTag() 从页面布局推断并创建一个 /StructTreeRoot——通过字体大小聚类识别标题,通过文本块识别段落,通过线条几何识别表格,以及针对每张图片的 /Figure 与 /Artifact 决策——并返回一个包含每种元素计数的 AutoTagReport。Document.GetStructTree() 读取生成的 StructTreeRoot,如果文档未标记则读取 null。
const report = doc.AutoTag({ lang: 'en-US', title: 'Quarterly Report', tables: true });
console.log(report.headings, report.paragraphs, report.tables, report.figures);
const tree = doc.GetStructTree();
console.log(tree ? tree.GetText() : '(no structure tree)');在 AutoTag() 已执行之后添加的内容——例如之后填充的页面——不在该遍历的覆盖范围内,也不会被双重标记。StructTreeRoot.Append() 和 StructElement.MarkContent() 是与启发式方法并行的手动创作 API:Append() 添加指定类型的子元素,MarkContent() 将页面区域标记为属于该元素。
技巧与最佳实践
- 在页面内容最终确定后运行一次
Document.AutoTag()——它不会重新标记之后添加的内容,因此请使用StructTreeRoot.Append()/StructElement.MarkContent()手动标记之后添加的任何内容。 - 将
opts.title传递给AutoTag()——它还会设置/ViewerPreferences /DisplayDocTitle,PDF/UA 在文档标题旁也需要该设置。 OutlineItem.Dest可以定位到明确的页面或已命名的目标({ name: '...' })——当目标页码可能因内容更改而移动时,请使用已命名的目标。- 当自动分页关闭时检查
AddTOCResult.remainder—— 目录框若对每个条目都太小,会悄悄停止绘制,而不是溢出框体。
常见问题
| 问题 | 原因 | 修复 |
|---|---|---|
Document.GetStructTree() 返回 null | 文档尚未标记 | 请先调用 Document.AutoTag() 或 Document.CreateStructTree() |
| 大纲项的链接无法解析 | 其 Dest 指向一个在 Document.GetNamedDestinations() 中不存在的目标 | 确认已命名的目标存在,或改用明确的页面 Dest |
Page.AddTOC() 在最后一个章节之前停止 | 目标 rect 对每个条目都太小,且 autoPaginate 已关闭 | 扩大 rect,传递 autoPaginate: true,或处理 AddTOCResult.remainder |
在 AutoTag() 之后添加的内容在结构树中缺失 | AutoTag() 只标记运行时出现的内容 | 手动为后续内容加标签,使用 StructTreeRoot.Append() / StructElement.MarkContent() |
FAQ
如何向 PDF 添加书签?
构建一个包含 OutlineItem 对象(Title、Dest,可选的 Children)的数组,并将其传递给 Document.SetOutlines()。
目录可以跨越多页吗?
是的 —— 在 Page.AddTOC() 的选项中传入 autoPaginate: true,或者检查 AddTOCResult.remainder 并在新页面上再次调用 AddTOC() 以处理未能容纳的内容。
如何使 PDF 可访问(已标记)?
调用 Document.AutoTag(),它会从页面布局中推断标题、段落、表格和图形。对于之后添加的内容,或需要纠正启发式结果的情况,请直接使用 StructTreeRoot.Append() 和 StructElement.MarkContent()。
页面编号目标和命名目标有什么区别?
页面编号目标(PageDest)指向明确的以 1 为起始的页码。命名目标(NamedDest)指向一个名称,查看器会通过文档自身的命名目标表进行解析——当目标页可能移动时,这非常有用。
API Reference 摘要
| 类/方法 | 描述 |
|---|---|
Document.GetOutlines() / Document.SetOutlines() | 读取或替换文档大纲(书签)树 |
OutlineItem | 一个大纲节点:Title,Dest,可选 Children |
Page.AddTOC() | 将目录渲染到页面矩形中 |
TOCEntry / AddTOCResult | 目录行的数据以及绘制结果 |
Document.GetNamedDestinations() | 列出文档中所有已命名的目标 |
Document.AutoTag() | 根据页面布局推断并编写标记结构树 |
Document.GetStructTree() / Document.CreateStructTree() | 读取或创建文档的 StructTreeRoot |
StructTreeRoot.Append() / StructElement.Append() / StructElement.MarkContent() | 手动编写或扩展结构树 |