
PDF 扁平化(Flatten) 是将文档中的交互对象转化为固定页面内容的过程。填写过的表单域会失去输入控件,但其值以文本形式保留在页面上;审阅者添加的批注会融入页面外观;可选内容图层则通过逐页重建可见内容来移除。扁平化后的文档不再依赖各 PDF 阅读器的交互功能来正确显示,不过页面内容本身仍可在 PDF 编辑器中修改。
本指南将介绍如何使用 Spire.PDF for .NET 在 C# 中扁平化 PDF:一次性扁平化整个表单、仅扁平化选定的表单域、逐页扁平化所有注释,以及通过 SVG 逐页重建来扁平化图层。最后附上验证步骤——因为扁平化后的文件在外观上与原始文件几乎一致,差异是结构性的,而非视觉上的。
快速导航
- 安装 Spire.PDF for .NET
- PDF 扁平化的作用
- 扁平化整个 PDF 表单
- 扁平化选定的 PDF 表单域
- 扁平化 PDF 注释与批注
- 通过 SVG 扁平化 PDF 图层
- 打印前扁平化 PDF
- 验证扁平化后的 PDF
- 完整示例:一步到位
- 常见问题
- 总结
1. 安装 Spire.PDF for .NET
Spire.PDF for .NET 已发布为 NuGet 包,可通过 .NET CLI 安装:
dotnet add package Spire.PDF
本文示例基于 Spire.PDF for .NET 12.9.18 版本构建。
你也可以下载 Spire.PDF for .NET并手动添加到项目中。
扁平化涉及三个命名空间:
| 命名空间 | 包含内容 |
|---|---|
Spire.Pdf |
PdfDocument、PdfPageBase、FileFormat |
Spire.Pdf.Widget、Spire.Pdf.Fields |
PdfFormWidget 和 PdfField -- 表单相关 |
Spire.Pdf.Interactive.Annotations |
PdfAnnotation、PdfAnnotationCollection -- 注释相关,包含 Flatten() |
2. PDF 扁平化的作用
扁平化将交互对象的外观压入页面内容,然后移除对象本身。目标是保留可见外观的同时去除交互性——这也是本指南通过统计保存文件中剩余对象数量来验证结果,而非对比截图的原因。
每种对象类型需要各自的调用方式:
| 对象 | 扁平化方式 |
|---|---|
| 表单域(文本框、复选框、列表框、下拉框) | PdfForm.IsFlatten 扁平化整个表单,PdfField.Flatten 扁平化单个域 |
| 注释(批注、高亮、图章、自由文本) | 对每个注释调用 PdfAnnotation.Flatten() |
| 图层(可选内容组) | PDF → SVG → PDF 转换,逐页从可见内容重建 |
3. 扁平化整个 PDF 表单
将 Form.IsFlatten 设为 true,文档中的所有表单域即在一次操作中完成转换:
using Spire.Pdf;
PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("FillableForm.pdf");
// 所有表单域及其当前值变为页面内容。
pdf.Form.IsFlatten = true;
pdf.SaveToFile("FlattenedForm.pdf");
pdf.Close();
以本文使用的登记表单为例——共五个域,其中三个已填写——扁平化后域数量从 5 降为 0,而填写值仍以可选中文本的形式保留在页面上。

4. 扁平化选定的 PDF 表单域
更常见的需求是仅锁定已填写完毕的域:让已完成的区域变为只读,其余部分仍可继续填写。通过 PdfFormWidget 的 FieldsWidget 集合访问各个域,每个域上的 Flatten 是一个布尔属性:
using Spire.Pdf;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;
PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("FillableForm.pdf");
PdfFormWidget formWidget = pdf.Form as PdfFormWidget;
foreach (PdfField field in formWidget.FieldsWidget)
{
// 扁平化已定稿的域,其余域保持可填写状态。
if (field.Name == "full_name" || field.Name == "email" || field.Name == "department")
{
field.Flatten = true;
}
}
pdf.SaveToFile("PartiallyFlattenedForm.pdf");
pdf.Close();
在示例表单上,此操作扁平化三个域,start_date 和 manager 仍可编辑——域数量从 5 降为 2。筛选条件取决于你表单中定义的域名——从 FieldsWidget 读取域名以确保精确匹配。

扁平化域与删除域的区别。 扁平化保留域的可见外观——包括其中输入的值——仅移除交互性。删除表单域则将对象及其外观一并移除。如果你的实际需求是删除,请参阅如何删除 PDF 表单域。
5. 扁平化 PDF 注释与批注
注释是审阅者在页面上方添加的对象:文本批注、高亮、图章和自由文本框。在 Spire.PDF for .NET 中,通过交互式注释模型进行扁平化:为每页创建 PdfAnnotationCollection,然后对每个注释调用 Flatten()。
using Spire.Pdf;
using Spire.Pdf.Interactive.Annotations;
PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile("ReviewedDocument.pdf");
foreach (PdfPageBase page in pdf.Pages)
{
if (page.Annotations.Count == 0) continue;
PdfAnnotationCollection annotations = PdfAnnotationCollection.Create(page);
// Flatten() 会从集合中移除注释,因此需倒序遍历。
for (int i = annotations.Count - 1; i >= 0; i--)
{
annotations[i].Flatten();
}
}
pdf.SaveToFile("FlattenedAnnotations.pdf");
pdf.Close();
本指南使用的合同审阅页包含三个注释——一个自由文本批注、一个橡皮图章和一条覆盖某条款的黄色高亮。循环结束后,注释数量从 3 降为 0,三个标记仍绘制在页面上:批注保留其文本框和颜色,图章保留其边框,高亮仍在文字下方。消失的只是交互对象本身。高亮之所以得以保留,是因为其四点坐标从已保存的页面中读取,而非从内存中的页面读取。

循环中有两个细节值得注意。倒序遍历集合:Flatten() 在扁平化每个注释时会将其从集合中移除,因此倒序循环确保每项恰好处理一次。Flatten() 声明在 Spire.Pdf.Interactive.Annotations.PdfAnnotation 上——这是注释集合创建时所基于的模型。
6. 通过 SVG 扁平化 PDF 图层
可选内容图层通过逐页从当前可见内容重建来移除:将每页转换为 SVG,再从 SVG 重新构建 PDF。这一往返过程重绘每页,在去除图层结构的同时保留文本为文本、图形为矢量——因此结果仍可选择、搜索和缩放,不同于渲染为图片的页面。
含图层的文档逐页转换:
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using Spire.Pdf;
// 每次运行使用独立的临时目录。
string workDir = Path.Combine(Path.GetTempPath(),
"flatten_svg_" + Guid.NewGuid().ToString("N").Substring(0, 8));
Directory.CreateDirectory(workDir);
try
{
// 步骤 1 - 将每页保存为独立的 SVG 文件。多页文档会生成
// page_1.svg、page_2.svg、...;单页文档生成 page.svg。
PdfDocument source = new PdfDocument();
source.LoadFromFile("LayeredDocument.pdf");
int pageCount = source.Pages.Count;
source.SaveToFile(Path.Combine(workDir, "page.svg"), FileFormat.SVG);
source.Close();
// 按页码排序文件;单页文档生成 page.svg。
string[] svgFiles = Directory.GetFiles(workDir, "*.svg")
.OrderBy(f =>
{
Match m = Regex.Match(Path.GetFileNameWithoutExtension(f), @"\d+$");
return m.Success ? int.Parse(m.Value) : 0;
})
.ToArray();
// 每页一个 SVG -- 下面的合并步骤依赖此数量。
if (svgFiles.Length != pageCount)
{
throw new IOException(
$"SVG 转换为 {pageCount} 页生成了 {svgFiles.Length} 个文件。");
}
// 步骤 2 - 将每个 SVG 加载回来并保存为单页 PDF。
string[] pageFiles = new string[svgFiles.Length];
for (int i = 0; i < svgFiles.Length; i++)
{
PdfDocument page = new PdfDocument();
page.LoadFromSvg(svgFiles[i]);
pageFiles[i] = Path.Combine(workDir, "page_" + (i + 1) + ".pdf");
page.SaveToFile(pageFiles[i], FileFormat.PDF);
page.Close();
}
// 步骤 3 - 按页序将单页 PDF 合并为一个文档。
PdfDocument.MergeFiles(pageFiles, "FlattenedLayers.pdf");
}
finally
{
Directory.Delete(workDir, true);
}
文件名取决于页数。两页的图层文档保存为 page.svg 时会生成 page_1.svg 和 page_2.svg,单页文档则生成不带编号的 page.svg。在示例文档上,往返过程按原页面大小保留两页顺序,保留各图层上绘制的文本,图层数量从 4 降为 0。同一工作流应用于单页文档则返回一页。

文本和矢量图形在往返过程中不受损,因此重建后的页面仍可像原始页面一样选择、搜索和缩放,各图层上绘制的内容留在页面上。
如需单独了解两个转换步骤,请参阅将 PDF 转为 SVG和将 SVG 转为 PDF。
7. 打印前扁平化 PDF
当文件将送往打印机、印刷店或你无法控制的阅读器时,应在打印前扁平化 PDF。一旦表单域、注释和图层变为普通页面内容,打印输出就由文件本身决定,而非阅读器的交互功能。
无需额外代码。文档包含表单域时使用表单部分的调用,包含批注时使用注释循环,图层可见性可能影响打印内容时使用 SVG 工作流——然后在发送前用下面的检查步骤验证输出。
8. 验证扁平化后的 PDF
扁平化后的文件外观与原始文件一致,因此应通过统计保存文件中的对象数量来确认结果,而非查看渲染效果:
using Spire.Pdf;
using Spire.Pdf.Fields;
using Spire.Pdf.Widget;
PdfDocument result = new PdfDocument();
result.LoadFromFile("FlattenedForm.pdf");
int fields = 0;
PdfFormWidget form = result.Form as PdfFormWidget;
if (form != null)
{
foreach (PdfField field in form.FieldsWidget) fields++;
}
int annotations = 0;
foreach (PdfPageBase page in result.Pages)
{
annotations += page.Annotations.Count;
}
Console.WriteLine($"pages={result.Pages.Count} fields={fields} "
+ $"annotations={annotations} layers={result.Layers.Count}");
result.Close();
通过 PdfFormWidget.FieldsWidget 统计表单域。在示例文件上,输出如下:
| 文件 | 页数 | 表单域 | 注释 | 图层 |
|---|---|---|---|---|
FillableForm.pdf(输入) |
1 | 5 | 0 | 0 |
FlattenedForm.pdf |
1 | 0 | 0 | 0 |
PartiallyFlattenedForm.pdf |
1 | 2 | 0 | 0 |
ReviewedDocument.pdf(输入) |
1 | 0 | 3 | 0 |
FlattenedAnnotations.pdf |
1 | 0 | 0 | 0 |
LayeredDocument.pdf(输入) |
2 | 0 | 0 | 4 |
FlattenedLayers.pdf |
2 | 0 | 0 | 0 |
然后检查视觉效果:在阅读器中打开文件,确认无法点击或切换任何内容,对比页数和页面大小与原始文件,选择或提取文本以确认其仍为真实文本。
9. 完整示例:一步到位
上面的代码片段各自解决一个问题。下面将它们整合为一个方法,一次性扁平化文档中的表单域和注释:
using Spire.Pdf;
using Spire.Pdf.Interactive.Annotations;
/// <summary>
/// 一次性扁平化 PDF 的表单域和注释。
/// </summary>
public static void FlattenDocument(string inputFile, string outputFile)
{
PdfDocument pdf = new PdfDocument();
pdf.LoadFromFile(inputFile);
// 1. 表单域:一个标志覆盖文档中的所有域。
if (pdf.Form != null)
{
pdf.Form.IsFlatten = true;
}
// 2. 注释:Flatten() 会逐个移除,因此倒序遍历。跳过
// 没有注释的页面。
foreach (PdfPageBase page in pdf.Pages)
{
if (page.Annotations.Count == 0) continue;
PdfAnnotationCollection annotations = PdfAnnotationCollection.Create(page);
for (int i = annotations.Count - 1; i >= 0; i--)
{
annotations[i].Flatten();
}
}
pdf.SaveToFile(outputFile);
pdf.Close();
}
条件判断确保循环仅处理携带注释的页面,因此同一方法也适用于仅含表单域的文档。使用该方法,五域表单的域数量降为 0,合同审阅页的注释从 3 降为 0。含图层的文档则需通过上一节的 SVG 工作流处理。
10. 常见问题
扁平化和删除表单域有什么区别?
扁平化保留域的可见外观——包括已输入的值——仅移除交互性。删除表单域则将域对象及其外观一并移除。
可以只扁平化部分表单域吗?
可以。将 pdf.Form 转型为 PdfFormWidget,遍历 FieldsWidget,对指定的域设置 Flatten = true。其余域保持可填写状态。
扁平化后文本仍可选择和搜索吗?
是的。扁平化后的文本域将其值保留在页面的文本层中,SVG 往返过程也保留其重绘的文本,因此值仍可选择、复制和搜索。
扁平化可逆吗?
扁平化将表单域和注释写入页面内容,因此如需后续编辑,请保留原始 PDF 文件。
打印前需要扁平化吗?
并非总是需要,但扁平化可以消除对阅读器的依赖。扁平化能使输出在不同 PDF 阅读器间更加一致,因为可见内容不再依赖于交互式表单域、注释或图层可见性设置。
11. 总结
在 C# 中扁平化 PDF,每种对象类型只需一次调用:
pdf.Form.IsFlatten = true扁平化文档中的所有域;PdfField.Flatten扁平化指定的域。PdfAnnotationCollection.Create(page)加上对每个注释倒序调用Flatten(),扁平化页面上的所有批注、高亮和图章。- 图层通过将每页保存为 SVG、加载回来再用
PdfDocument.MergeFiles合并来扁平化。
无论哪种方式,页面外观都得以保留,因此应通过统计保存文件中剩余的对象数量来验证——而非对比截图。
如需在真实业务环境中测试 Spire.PDF for .NET,可以申请临时许可证。







