该页面可以包含自动翻译的文本。
如何使用 C# 和 VB.NET 创建 PDF 文档
本文介绍了借助 Docotic.Pdf 库在 .NET 中创建 PDF 文档的几种方法。Docotic.Pdf 是一个高性能的纯 C# .NET 库,无需任何外部依赖,即可用于创建、编辑、转换和处理 PDF 文档。

以下各节将介绍使用 Docotic.Pdf 创建 PDF 的主要方法:
- 使用核心 API,它提供对文本、图形和 PDF 内部结构的底层控制。此选项最适合自定义布局、包含大量图形的文档以及需要高级功能的文档。
- 使用高级布局 API,它支持段落、表格、页眉、页脚和自动分页。如果您需要结构化的文档而无需手动计算位置,此 API 是理想之选。
- 将 HTML 转换为 PDF,并支持 SVG 和其他 Web 格式。如果您的解决方案已经生成 HTML 文档,并且您需要这些 HTML 和 CSS 文件的 PDF 版本,则此方法尤其有用。
- 从图像创建 PDF。此方法适用于扫描文档、基于图像的报告、收据以及任何以栅格图像开始的工作流程。
- 合并或拆分 PDF。这对于组装报告、处理用户上传内容、合并相关文档以及重构大型 PDF 来说是一个不错的选择。
- 从模板创建 PDF。当需要批量生成格式一致的文档(例如收据、税务表格、雇佣合同和其他可重复使用的文档类型)时,这种方法非常有效。
本指南还涵盖以下主题:
使用 Core API 创建 PDF
Core API 是 Docotic.Pdf 创建 PDF 的基础。它通过 Canvas API 提供对 PDF 画布上文本、图像和矢量图形放置的完全底层控制。此绘图 API 是 Core API 的一个子集,提供用于向页面和其他带有画布的对象添加内容的各种方法和属性。除了渲染之外,Core API 还支持注释、表单字段、图层、书签和其他 PDF 功能。
以下 C# 代码使用三个基本操作创建了一个简单的 PDF:在页面画布上绘制文本、放置图像和渲染矢量图形。
using var pdf = new PdfDocument();
var canvas = pdf.Pages[0].Canvas;
canvas.Font = pdf.CreateFont(PdfBuiltInFont.HelveticaBold);
canvas.FontSize = 14;
canvas.DrawString(40, 100, "Core API demo: text, images, and vector graphics");
var image = pdf.CreateImage("image.png");
canvas.DrawImage(image, 40, 180, 120, 120, 0);
canvas.Pen.Color = new PdfRgbColor(30, 60, 160);
canvas.Pen.Width = 2;
canvas.Brush.Color = new PdfRgbColor(200, 230, 255);
canvas.DrawRectangle(new PdfRectangle(200, 200, 150, 80), PdfDrawMode.FillAndStroke);
pdf.Save("core-api-demo.pdf");
本概述仅介绍了 Core API 功能的一小部分。如需了解高级主题,请参阅关于使用 Core API 构建 PDF 的详细文章。该文章涵盖了文本测量、色彩空间处理、裁剪、图案填充、透明度处理以及其他功能。
使用 Layout API 生成 PDF
Layout API 是一个高级文档构建引擎,它提供了一种最简单高效的方式来生成内容丰富的复杂 PDF 文件。
使用此 API 时,您可以从页面、容器、文本跨度、图像、表格、链接、页眉、页脚等结构元素构建 PDF 文件。您无需计算坐标或手动管理分页,只需描述文档结构,其余工作都由布局引擎处理。
此示例演示了如何使用 Layout API 创建 PDF 文件,并采用声明式布局而非手动定位。
PdfDocumentBuilder.Create()
.Info(info => info.Title = "Docotic.Pdf Layout API demo")
.Generate("layout-api-demo.pdf", doc => doc.Pages(pages =>
{
pages.Content().Padding(100).Text(text =>
{
text.Span("The Layout API lets you compose PDFs from structural elements ");
text.Line("without manually calculating coordinates or handling pagination.")
.Style(s => s.Strong);
});
}));
查看详细指南,了解如何在 .NET 应用程序中使用 Layout API 生成 PDF。
使用 HTML 转 PDF API 将 Web 内容转换为 PDF
Docotic.Pdf 及其免费的 HtmlToPdf 插件提供了一个现代化的、高质量的基于 Chrome 的 HTML 转 PDF 引擎。您可以使用该插件提供的 API,将现代 HTML 和其他网页内容(例如 SVG 或 WebP 图像)转换为高质量的 PDF 文档。
HTML 转 PDF API 可以从完整的 HTML 页面或 HTML 片段创建 PDF。您可以转换来自 URL、原始 HTML 字符串和本地 HTML 文件的内容。后两种方式可以轻松地从 HTML 模板生成 PDF。
查看如何从 HTML 模板生成 PDF 的示例:
public static async Task HelloHtmlTemplate()
{
static string GetUserName()
{
// Replace with real logic: form input, API call, config, etc.
return "World";
}
string html = $@"
<h1>Hello, {GetUserName()}!</h1>
<p>This PDF was generated from an HTML template.</p>";
using var converter = await HtmlConverter.CreateAsync();
using var pdf = await converter.CreatePdfFromStringAsync(html);
pdf.Save("hello-html-template.pdf");
}
有关更多详细信息和示例,请查看我们的深入HTML-to-PDF概述。
从图像创建 PDF
Docotic.Pdf 提供了一种灵活且对开发者友好的方式来将图像转换为 PDF。该库通过核心 API 支持 JPEG、BMP、GIF、PNG、TIFF 和 JPEG 2000 图像格式。

当 PDF 格式支持时,Docotic.Pdf 会直接嵌入图像字节,避免像素解码和重新编码,从而保留原始压缩效果。该库还会尽可能保留色彩空间。
此外,通过 HTML 转 PDF API,Docotic.Pdf 还支持 SVG 和 WebP 格式。当您需要将图像与标签或描述并排显示时,Layout API 可以帮助您轻松排列和对齐元素。
如何将多张图片合并到一个PDF文件中
使用 Docotic.Pdf,您可以轻松地将一组图像转换为单个 PDF 文件,并将每张图片放置在单独的页面上。
以下示例从文件中加载图像,并将每张图片绘制在单独的页面上。每张图片都会缩放以适应页面,并居中显示,从而实现简洁一致的布局。
public static void ImagesOnToPdf(string[] imagePaths, string outputPath)
{
using var pdf = new PdfDocument();
foreach (string path in imagePaths)
{
var image = pdf.CreateImage(path);
var page = pdf.AddPage();
var pageWidth = page.Width;
var pageHeight = page.Height;
var scale = Math.Min(pageWidth / image.Width, pageHeight / image.Height);
var drawWidth = image.Width * scale;
var drawHeight = image.Height * scale;
var x = (pageWidth - drawWidth) / 2;
var y = (pageHeight - drawHeight) / 2;
page.Canvas.DrawImage(image, x, y, drawWidth, drawHeight, 0);
}
pdf.RemovePage(0);
pdf.Save(outputPath);
}
处理多页 TIFF 和 GIF 图像
Docotic.Pdf 完全支持多页 TIFF 和 GIF 文件。向 PDF 添加图像时,如果任何图像可能包含多页,请使用 OpenImage 方法而不是 CreateImage 方法。
以下代码演示了如何将 TIFF 转换为 PDF,它适用于单页和多页图像:
public static void OddFramesToPdf(string[] imagePaths, string outputPath)
{
using var pdf = new PdfDocument();
foreach (string path in imagePaths)
{
var imageFrames = pdf.OpenImage(path);
for (int i = 0; i < imageFrames.Count; i++)
{
if (i % 2 != 0)
continue;
var image = pdf.CreateImage(imageFrames[i]);
var page = pdf.AddPage();
page.Width = image.Width;
page.Height = image.Height;
page.Canvas.DrawImage(image, 0, 0, image.Width, image.Height, 0);
}
}
pdf.RemovePage(0);
pdf.Save(outputPath);
}
您可以使用相同的方法将 GIF 转换为 PDF。这种方法也适用于其他图像格式,但对于仅包含单帧的格式来说,操作会稍微复杂一些。
合并和拆分PDF
作为一款功能齐全的 .NET 库,Docotic.Pdf 可以通过合并、提取和重新组织现有文档中的页面来创建新的 PDF 文件。
合并 PDF 文件时,该库不仅会添加来自其他文档的页面,还会附加图层、书签、页面标签、共享 JavaScript、目标位置(链接目标)和嵌入文件。有关更多详细信息以及如何减小合并后 PDF 文件大小的指导,请参阅合并 PDF 文章。
数字签名的 PDF 文件无法合并,否则会使现有签名失效。为了保留签名,请创建 PDF 文件包,而不是附加文档。另一种方法是先合并 PDF 文件,然后对合并后的文档应用新的数字签名。
Docotic.Pdf 还允许您将页面复制并提取到新文档中。与复制页面关联的所有内容都会被保留,包括注释、表单控件、结构化内容、图层和其他相关数据。有关实际示例,请参阅关于在.NET中拆分PDF的文章。该文章还解释了如何提取或删除页面。
使用 PDF 模板
PDF模板是预先设计好的PDF文件,可作为创建新文档的基础结构。当您需要生成布局一致的PDF文件,同时提供不同的数据时,PDF模板非常有用。如果您希望将视觉设计与数据本身分离,PDF模板也是一个不错的选择。
模板可以是表单式PDF,也可以是不带表单的静态PDF。两种类型的模板用途相同。此外,表单式模板包含交互式元素,如果不进行扁平化处理,这些元素可能会收集用户信息。
从基于表单的模板创建 PDF
基于表单的模板通常包含 AcroForms,这是一种标准且广泛支持的交互式 PDF 表单类型。要从此类模板生成 PDF,通常需要:
- 填写每个占位符字段
- 展平字段以防止进一步编辑
- 将结果另存为新的 PDF
以下 C# 代码通过名称查找占位符文本字段,为其赋值,展平字段,并保存结果,从而从模板创建 PDF:
var nameOnCertificate = "Eva Marin";
using var pdf = new PdfDocument("certificate-template.pdf");
if (pdf.TryGetControl("name", out var field))
{
if (field is PdfTextBox nameField)
{
nameField.Text = nameOnCertificate;
nameField.Flatten();
}
}
pdf.Save($"certificate-{nameOnCertificate}.pdf");
如果您的模板包含许多占位符,您可以导入 FDF 数据,而无需逐个填写每个字段。您还可以使用 PdfDocument.FlattenControls 一次性展平所有字段。
无需表单即可从静态模板创建 PDF
如果您的模板不包含表单字段,您将直接在页面画布上绘制名称和其他数据。静态 PDF 模板通常包含固定的视觉占位符,例如文本、图像或空白区域。要从模板生成 PDF,您需要以编程方式填充这些空白区域并替换占位符文本和图像。
空白区域
使用 Canvas API 在空白区域放置文本和图像。在只需要进行少量更改(例如添加姓名和照片)的简单情况下,这种方法非常有效。您需要知道区域的坐标和大小,并且为了正确放置文本,可能需要先测量文本长度,然后再进行相应的对齐。
处理可变长度或多行文本更具挑战性,但仍然可行。通过结合使用 DrawText、DrawString 和文本测量方法,您可以根据需要自动换行和定位文本。如果您的模板包含多个此类区域,请考虑使用其他方法,例如使用 Layout API 生成 PDF。
占位符文本
Docotic.pdf 也提供了查找和替换文本的方法。然而,使用文本搜索作为模板机制通常并不比处理空白占位符区域更容易。在插入新内容之前,您必须找到确切的文本片段并将其干净地删除。
占位符图片
静态模板可能包含用户头像或产品照片的占位符图片。要查找占位符图片,请枚举每个页面上绘制的图片集合。对于每张图片,您可以获取其可见尺寸和位置。要替换占位符,请使用 PdfImage.ReplaceWith 方法。
using var pdf = new PdfDocument("invoice-template.pdf");
var paintedImages = pdf.Pages[0].GetPaintedImages();
var placeholder = paintedImages.First();
placeholder.Image.ReplaceWith("company-logo.jpg");
pdf.Save($"invoice.pdf");
另一种方法是在占位符图像所在的区域绘制新图像,但这通常会毫无理由地增加生成的 PDF 的大小。
设计易于替换的占位符
对于静态模板,设计布局时,最好使用可预测且定义清晰的区域来放置文本和图像。在包含可变长度内容的区域周围留出足够的边距,并使用与预期插入的宽高比相匹配的中性占位符图像。
如果您的模板使用了计划替换的占位符文本,则可以使用文本框代替纯文本来简化工作流程。在模板中添加一个只读的无边框文本框,并将占位符文本放在其中。生成最终 PDF 时,打开模板,按名称找到文本框,然后使用 box.Text = "新文本"; 直接赋值。之后,将文本框展平以防止进一步编辑。
添加交互元素
交互式功能可将静态 PDF 转换为动态、易于浏览的文档,并添加注释和标记。操作和 JavaScript 可直接在 PDF 中实现自动化。
注释
注释是附加到页面上的对象,用于表示评论、高亮显示、文件附件和其他交互式控件。它们在页面内容中可见,并支持审阅工作流程和协作。
以下 C# 示例演示了如何使用 Docotic.Pdf 向 PDF 页面添加文本注释(也称为便签)。
using var pdf = new PdfDocument("example.pdf");
var page = pdf.Pages[0];
var textAnnot = page.AddTextAnnotation(new PdfPoint(50, 100), "Reviewer comment");
textAnnot.Contents = "Please check the figures on this page.";
pdf.Save("text-annotation.pdf");
下一个示例演示了如何突出显示文本和其他内容,以引起人们对文档关键部分的注意。
using var pdf = new PdfDocument("example.pdf");
var page = pdf.Pages[0];
var color = new PdfRgbColor(255, 255, 120);
var annotationText = "Please confirm this part.";
var bounds = new PdfRectangle(50, 250, 120, 40);
page.AddHighlightAnnotation(annotationText, bounds, color);
pdf.Save("highlight-annotation.pdf");
链接
PDF 标准定义了多种类型的 PDF 链接。其中最重要且应用最广泛的是内部链接和超链接。
内部链接(也称为 GoTo 操作)允许跳转到同一 PDF 文档内的页面或指定目标位置。它们对于交叉引用和内部导航非常有用。
以下 C# 代码创建了一个从首页到索引为 5 的页面的链接:
using var pdf = new PdfDocument();
var page = pdf.Pages[0];
int targetPageIndex = 5;
for (int i = 0; i < targetPageIndex; i++)
pdf.AddPage();
var rect = new PdfRectangle(50, 50, 100, 40);
page.Canvas.DrawRectangle(rect);
page.AddLinkToPage(rect, targetPageIndex);
pdf.Pages[targetPageIndex].Canvas.DrawString(50, 50, "Glad to have you here.");
pdf.Save("link-to-page.pdf");
布局 API 提供了另一种创建内部链接的方法,无需绝对定位。
外部链接(也称为 URI 操作)会打开一个网页 URL。您可以使用 PdfPage.AddHyperlink 方法向 PDF 页面添加超链接。除此之外,其方法与内部链接相同。
书签
书签(也称大纲)是特殊的快捷方式或链接,可以帮助读者快速导航到特定章节或页面。当读者点击书签时,查看器应用程序会跳转到文档的指定位置。
大纲显示在查看器的书签面板中,并提供类似于书籍目录的层级式导航树,但具有交互性。PDF 大纲可以包含主书签和嵌套书签,从而更轻松地组织大型文档。
以下示例展示了如何使用 C# 和 Docotic.Pdf 在 PDF 中创建书签。该代码创建了三个顶级书签。第二个书签包含一个嵌套书签。
using var pdf = new PdfDocument();
for (int i = 0; i < 5; i++)
{
var page = i == 0 ? pdf.Pages[0] : pdf.AddPage();
var canvas = page.Canvas;
canvas.FontSize = 14;
canvas.DrawString(50, 50, $"Page {i + 1}");
}
var root = pdf.OutlineRoot;
root.AddChild("Getting Started", 1);
var child = root.AddChild("Things You Can Do", 2);
child.AddChild("Making Quick Improvements", 3);
root.AddChild("Keeping Everything Running Smoothly", 4);
pdf.PageMode = PdfPageMode.UseOutlines;
pdf.Save("bookmarks.pdf");
书签与纸质书籍页面上印刷的目录或 PDF 文件中显示的目录有所不同。您可以通过测量标题并写入带有页码的条目,以编程方式创建目录。
要查看使用 Layout API 创建目录的另一种方法,请查看我们示例存储库中的相关代码。
PDF脚本
JavaScript 操作是最强大的交互功能之一。PDF JavaScript 是 JavaScript 的一个子集,它公开了文档和查看器 API。它用于表单验证、计算、用户界面对话框和小型自动化任务。
您可以将脚本附加到注释、书签、表单控件或打开操作。使用 Docotic.Pdf,您可以将 JavaScript 代码嵌入到 PDF 中。该代码可以验证表单输入、计算值、显示或隐藏字段,或执行查看器交互。
共享 JavaScript 集合包含存储在文档级别的脚本。这些脚本可以被多个操作重用。换句话说,共享脚本对于实用函数和共享逻辑非常有用。它们有助于减少重复代码并简化维护。
以下代码定义了一个共享脚本,该脚本在 PDF 查看器中显示一条警告消息,然后演示如何通过将其分配给按钮的单击操作来触发该脚本。
using var pdf = new PdfDocument();
pdf.SharedScripts.Add(
pdf.CreateJavaScriptAction("function messageBox(message) { app.alert(message,3); }")
);
var button = pdf.Pages[0].AddButton(50, 50, 100, 40);
button.Text = "Click me";
button.OnMouseUp = pdf.CreateJavaScriptAction("messageBox('Hello, dear!');");
pdf.Save("shared-javascript.pdf");
示例中的脚本很简单,但您可以构建任何复杂度的 JavaScript 操作。Adobe JavaScript API 参考文档提供了许多可用的方法。请注意,非 Adobe 的查看器通常只支持 API 的一部分。
开放行动
打开操作是指 PDF 查看器在打开文档时执行的操作。典型用途包括打开到特定页面、运行 JavaScript 初始化例程或设置查看器首选项。打开操作的类型没有限制。
以下示例展示了如何创建 GoTo 打开操作。该代码在第二页添加文本,并设置一个打开操作,使查看器在打开 PDF 时自动导航到该页面。
using var pdf = new PdfDocument();
var canvas = pdf.AddPage().Canvas;
canvas.FontSize = 14;
var message =
"If you see this immediately after opening the file, " +
"your PDF viewer supports open actions.";
var options = new PdfTextDrawingOptions(new PdfRectangle(100, 100, 100, 150));
canvas.DrawText(message, options);
pdf.OnOpenDocument = pdf.CreateGoToPageAction(1, 0);
pdf.Save("open-action.pdf");
请注意,并非所有查看器都会执行 JavaScript 打开操作。有些查看器会忽略这些操作,或者先提示用户。有些查看器则会完全阻止打开操作。
要检查 PDF 是否包含打开操作,请将其加载到 PdfDocument 对象中,并检查 OnOpenDocument 属性。如果该属性为 null,则表示该文档未定义打开操作。
应用加密和数字签名
加密和数字签名分别从两个互补的角度保护您创建的 PDF 文件。加密控制谁可以打开文档以及他们可以对文档执行哪些操作,而签名则证明文件的创建者或批准者,并确认文件未被篡改。
密码保护允许您在创建过程中设置访问规则。您可以设置一个打开密码来限制查看权限,并设置一个所有者密码来定义诸如打印、复制、编辑或填写表单等权限。证书加密提供更强大的、针对特定收件人的保护,尤其适用于在不依赖共享密码的情况下向多人分发机密 PDF 文件。更多详情,请参阅关于使用密码和证书加密 PDF 的文章。
数字签名在创建时即可增强文件的真实性和完整性。Docotic.Pdf 可以使用来自文件、Windows 应用商店、硬件令牌、HSM 或云密钥服务的证书对 PDF 文件进行签名。您可以添加时间戳和长期验证数据,以便签名在文档生成后很长时间内仍然可验证。此外,Docotic.Pdf 还支持外部签名工作流程,包括 PKCS#11 和云 KMS。
设置 PDF 元数据
PDF 元数据是嵌入在文档中的描述性信息,例如标题、作者、主题、关键词、创建日期等字段。它可以帮助软件、搜索引擎和文档管理系统在不打开文件的情况下了解文件的内容。
PDF 文档可以同时包含两种元数据系统:
- XMP 元数据
- 文档信息字典(
Info字典)

XMP 是一种更丰富、结构化且标准化的元数据嵌入格式。Info 字典虽然简单且应用广泛,但功能有限,已被 PDF 2.0 标准 (ISO 32000-2) 弃用,取而代之的是 XMP 元数据。Docotic.Pdf 可以读写这两种系统,并提供一个辅助方法来保持它们同步。
Docotic.Pdf 会在保存 PDF 文件之前自动更新一些元数据。例如,该库默认设置 Producer 和 Creator 的值。使用 保存选项 可以更改此行为并保留显式设置的元数据值。
XMP元数据
使用 PdfDocument.Metadata 属性可以访问和修改 PDF 中的 XMP 元数据。通过此属性,您可以处理诸如 XMP Core、Dublin Core 和 PDF 模式等常用模式,以及管理您自己的自定义元数据。
using var pdf = new PdfDocument();
var xmp = pdf.Metadata;
xmp.Pdf.Creator = new XmpString("Second-line authoring terminal");
xmp.Pdf.Title = new XmpString("Quarterly Report");
var creators = new XmpArray(XmpArrayType.Ordered);
creators.Values.Add(new XmpString("Second-line authoring terminal"));
creators.Values.Add(new XmpString("Assistive authoring terminal"));
xmp.DublinCore.Creators = creators;
var descriptions = new XmpArray(XmpArrayType.Alternative);
descriptions.Values.Add(new XmpLanguageAlternative("x-default", "Quarterly Report"));
descriptions.Values.Add(new XmpLanguageAlternative("fr", "Rapport trimestriel"));
descriptions.Values.Add(new XmpLanguageAlternative("de", "Quartalsbericht"));
xmp.DublinCore.Descriptions = descriptions;
var author1 = new XmpString("First Author");
author1.Qualifiers.Add("role", "main author");
var author2 = new XmpString("Second Author");
author2.Qualifiers.Add("role", "co-author");
var authors = new XmpArray(XmpArrayType.Unordered);
authors.Values.Add(author1);
authors.Values.Add(author2);
xmp.Custom.Properties.Add("authors", authors);
pdf.Save("with-xmp-metadata.pdf");
XMP 支持数组、结构体和类型化值,因此非常适合存储丰富的元数据。上面的代码还展示了如何在自定义 XMP 模式中存储应用程序特定的属性。
文档信息词典
Info 字典主要用于存储文本字符串值。它体积小巧,支持范围广,但功能有限。使用 Info 字典是为了兼容旧版工具,其他情况下建议使用 XMP。
同步元数据
为了避免因数据不一致而导致读者和自动化工具出现困惑,保持两个元数据系统同步是一种良好的实践。
使用 PdfDocument.SyncMetadata 来对齐 XMP 和 Info 值,使对应的字段匹配。该方法会从 XMP 填充缺失的 Info 属性,同样地,也会从 Info 填充缺失的 XMP 字段。如果 XMP 是权威数据源,请设置 preferXmp: true;如果应优先使用 Info 字典,请设置 preferXmp: false。
pdf.SyncMetadata(preferXmp: true);
有关该方法同步哪些属性的详细信息,请参阅SyncMetadata文档中的“备注”部分。
配置页面标签和查看器首选项
新创建的 PDF 文件可以通过明确的页码、精细的查看器设置以及选择合适的页面布局来更有效地呈现文档内容。这些设置会影响读者首次查看和浏览文件的方式。
页面标签
页面标签是元数据,用于告知 PDF 查看器每页应显示哪个标签。当可见页码必须与实际页码不同时,请使用页面标签。例如,您希望 PDF 的前言部分使用 i, ii, iii,而正文部分使用 1, 2, 3。
以下 C# 代码演示了如何为 PDF 页面添加标签:前三页使用小写罗马数字,其余页面使用从 1 开始的阿拉伯数字。
using var pdf = new PdfDocument();
for (int i = 0; i < 8; i++)
pdf.AddPage();
pdf.PageLabels.AddRange(0, 2, PdfPageNumberingStyle.LowercaseRoman);
pdf.PageLabels.AddRange(3, PdfPageNumberingStyle.DecimalArabic);
pdf.Save("with-page-labels.pdf");
PDF 查看器偏好设置
PDF 查看器首选项是嵌入在文档中的建议,用于指导查看器如何显示文档。例如,您可以指定查看器应隐藏工具栏、将窗口居中或调整窗口大小以适应页面。查看器首选项是对页面布局和打开操作设置的补充。
以下是如何使用 Docotic.Pdf 更改 PDF 查看器首选项:
using var pdf = new PdfDocument();
pdf.ViewerPreferences.DisplayTitle = false;
pdf.ViewerPreferences.FitWindow = true;
pdf.ViewerPreferences.HideToolBar = true;
pdf.ViewerPreferences.HideMenuBar = true;
pdf.ViewerPreferences.HideWindowUI = true;
pdf.ViewerPreferences.CenterWindow = true;
pdf.Save("with-viewer-prefs.pdf");
请注意,根据配置的不同,Adobe Acrobat 和其他查看器可能会忽略这些首选项。
页面布局和页面模式
页面布局决定了文档打开时的页面排列方式:一次显示一页、单栏连续显示或跨页显示。页面模式控制打开时显示哪些用户界面面板:书签/大纲、附件、缩略图或不显示任何面板。
以下是如何指定创建的 PDF 文件以跨页显示(左页在前)并在打开时显示缩略图面板:
using var pdf = new PdfDocument();
for (int i = 0; i < 7; i++)
{
var page = i > 0 ? pdf.AddPage() : pdf.Pages[0];
page.Canvas.FontSize = 36;
page.Canvas.DrawString(100, 100, $"Page {i + 1}");
}
pdf.PageLayout = PdfPageLayout.TwoPageLeft;
pdf.PageMode = PdfPageMode.UseThumbs;
pdf.Save("with-layout-and-mode.pdf");
保存PDF文件
Docotic.Pdf 可以从您创建或编辑的同一文档生成不同的 PDF 文件或流。这些输出可能符合不同版本的 PDF 格式,字节长度各异,并且生成所需的内存量也不同。
库生成 PDF 字节的方式取决于保存选项。如果您未显式指定保存选项,则 PdfDocument 对象的 Save、SignAndSave 和 TimestampAndSave 方法将使用默认设置。这些默认值经过精心选择,适用于大多数场景,但您可能仍然需要进行调整。
有关可用选项及其默认值的详细信息,请参阅 PdfSaveOptions 类 的文档。以下部分重点介绍一些更重要的选项并提供实用建议。
PDF版本
Docotic.Pdf 默认使用对象流来提高文件压缩率。因此,该库默认创建 PDF 1.5 文件和流。
PDF 1.5 需要 Adobe Reader 6(2003 年发布)或更高版本才能查看生成的文档。这通常不是问题,除非您需要支持仅接受旧版 PDF 的旧工具、旧版查看器或嵌入式设备。
以下是如何保存为旧版 PDF 文件:
using var pdf = new PdfDocument();
var options = new PdfSaveOptions
{
Version = PdfVersion.Pdf14,
UseObjectStreams = false,
};
pdf.Save("version-1.4.pdf", options);
要使用 PDF 1.4 版本保存,必须同时禁用对象流。如果文档包含更高版本中引入的功能,库将不会使用旧版本。
文件大小缩减
几个保存选项设置为 true 时,Docotic.Pdf 会生成更小的文件(按字节计):RemoveUnusedObjects、OptimizeIndirectObjects、WriteWithoutFormatting 和 UseObjectStreams。
以下是如何生成不包含未引用对象和多余空白的 PDF 文件,并将数据紧密地打包到对象流中:
using var pdf = new PdfDocument();
var options = new PdfSaveOptions
{
UseObjectStreams = true,
RemoveUnusedObjects = true,
OptimizeIndirectObjects = true,
WriteWithoutFormatting = true,
};
pdf.Save("optimized.pdf", options);
这些选项在 PDF 文件完全重写时最为有效。在增量保存过程中,它们仅适用于新添加的版本,无法清理或优化文件的早期部分。
增量更新
Docotic.Pdf 可以增量更新 PDF 文件。当 WriteIncrementally 设置为 true 时,库会将更改追加到现有文件,而不是重写它。之前的交叉引用和对象数据将保持不变。追加的数据称为增量更新,当前更新与所有先前的更新共同构成文件的新版本。
对于新创建的文档,无法进行增量更新,因为没有先前的版本可供追加。库会忽略此选项,并以非增量模式写入新文档。
需要增量更新时
当向已包含签名的文档添加新的数字签名时,必须以增量方式保存文件。同样,当使用新的注释或表单数据更新先前已签名的文件时,也应如此。在这些情况下,重写整个文件会使现有签名失效。
同时,最好在应用第一个数字签名之前执行非增量(完整)保存,以确保签名基线是一个干净的、完全重写的文件。如果文档的早期版本存在结构性问题,则对其进行签名可能会导致意外的签名验证问题。
在需要保留可审计的修订历史记录或强制执行仅追加文档存储的工作流程中,也需要使用增量追加。
使用增量更新的好处
增量更新允许在同一文件上进行多次签名,并允许在签名后进行有限的修改,例如填写表单字段,而不会使现有签名失效。
此外,这种方法对于小幅更改的保存速度更快,因为只会写入修改后的数据。它还能保留文档的修订历史记录,这对于审计和其他合规性工作流程至关重要。
需要避免的问题和陷阱
增量更新无法对整个文件进行全局压缩或移除过时的对象,因为它只会追加修改后的对象。因此,与完全重写相比,增量更新通常会生成更大、优化程度更低的文件。
即使没有未使用的对象,文件大小也会随着每次修订而增加,因为所有先前的修订都仍然嵌入在文件中,并持续占用空间。
早期修订中的敏感或错误信息仍然可以恢复,并且追加新数据并不能修复先前修订中存在的 PDF 格式问题或结构缺陷。
最后,一些查看器和处理工具难以处理多版本 PDF 文件。在依赖增量更新之前,请确保所有文档使用者都能处理包含多个修订版本的文件。
测试 PDF 输出
自动化 PDF 测试通过将生成的 PDF 与存储在代码库或工件存储中的基准 PDF 进行比较,防止版本发布过程中出现内容和布局方面的回归问题。基准 PDF 有助于检测文本、字体、图像或布局中的意外更改,从而减少每次构建时进行手动质量保证 (QA) 的需求。
结合结构检查、文本提取和视觉比较,可获得最可靠的结果。
方法快速比较
| 方法 | 速度 | 敏感性 | 最适合 |
|---|---|---|---|
| 结构比较 | 快速地 | 高:能够检测到对象级别的变化 | 回归测试必须确认同一文档的两个版本在结构上完全相同 |
| 文本提取 | 快速地 | 中等:通常忽略布局更改 | 验证语义内容和表格 |
| 视觉差异 | 慢点 | 高:能够同时检测到内容和渲染/布局的变化 | 捕捉视觉退化 |
比较文档结构
使用 PdfDocument.DocumentsAreEqual 方法可以比较 PDF 对象图、PDF 版本和文档安全存储 (DSS),同时忽略与时间相关的文档属性。该方法还会忽略文档元数据、尾部 ID 和其他自动生成的属性。
此方法非常适合必须确保没有添加或删除意外对象的 PDF 文档测试工作流程。DocumentsAreEqual 支持文件和流重载,并且可以比较加密的 PDF。
Docotic.Pdf 示例中提供了一个演示此技术的完整示例。除了展示如何在常规 .NET 应用程序中使用此方法外,该示例还演示了如何在原生 AOT 应用程序中使用 DocumentsAreEqual。
通过提取的文本验证 PDF
一次性从整个文档中提取文本,或逐页提取,并比较字符串。您可以使用文本提取选项来微调提取过程,例如排除包含页脚的矩形区域。为了便于比较,您可以将提取的文本拆分为行或单词。
对于结构化检查,首先提取包含位置、字体以及其他详细信息的文本块、单词或字符。然后将每个提取的元素与相应的基线元素进行比较。
检测视觉差异
首先将 PDF 页面渲染成图像,并将每张图像与基准图像进行比较。使用 ImageSharp.Compare 或 Magick.NET 等专用库来检测图像差异。
最好进行严格的逐像素比较,确保两张图像中每个对应的像素都完全一致。如果允许存在较小的渲染差异,可以调整比较逻辑以容忍细微差别,但精确的像素相等性能够提供最可靠的结果。
可以考虑使用哈希值作为快速预检,在不进行完整像素比较的情况下判断两张图像是否可能相同。计算每张渲染图像的 SHA-256 哈希值,如果哈希值匹配,则几乎可以肯定两张图像相同。如果哈希值不同,则进行完整的逐像素比较。
结论
Docotic.Pdf 提供了一套全面的多层工具包,用于在 .NET 中创建和操作 PDF 文件。开发人员可以选择使用 Core API 进行底层控制,使用 Layout API 进行高级文档生成,或者将 HTML 转换为 PDF,以适应已基于 Web 技术构建的工作流。
该库还支持基于图像的 PDF、模板驱动生成,以及丰富的交互式功能,例如注释、链接、书签、JavaScript 操作和打开操作。
为了确保可靠性,Docotic.Pdf 包含用于测试 PDF 输出的方法,从而避免应用程序中的更改引入回归问题或意外差异。