两分钟内生成您的第一个 PDF
Majorsilence.Pdf 是一个面向 .NET 8 和 10 的零依赖、自包含 PDF 库。只有一个包,无原生二进制文件——创建文档、绘制文本、形状和图像、嵌入 TrueType 字体,并保存到文件或流。
安装
一个单独的 NuGet 包——无需任何配套库:
# .NET CLI dotnet add package Majorsilence.Pdf # Package Manager Console Install-Package Majorsilence.Pdf
Hello, PDF
使用 PdfDocument.Create() 创建文档,添加一页,在画布上绘制,然后保存。坐标单位为 PDF 点(1 pt = 1/72 英寸),原点位于左上角——Y 值向下递增。
using Majorsilence.Pdf; PdfDocument.Create() .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Hello, PDF!", 72, 72, TextStyle.Default.WithSize(24).WithBold()); canvas.DrawLine(72, 96, 520, 96); }) .Save("hello.pdf");
API 风格——回调式与增量式
选择最适合您代码结构的方式:
回调风格(流式链)
向 AddPage 传入一个绘制 lambda。文档本身会被返回,以便继续链式调用。最适合一次性构建的短文档。
PdfDocument.Create()
.WithTitle("My Report")
.AddPage(PageSizes.A4, canvas =>
{
canvas.DrawText("Chapter 1", 72, 72,
TextStyle.Default.WithSize(18).WithBold());
})
.AddPage(PageSizes.A4, canvas =>
{
canvas.DrawText("Chapter 2", 72, 72,
TextStyle.Default.WithSize(18).WithBold());
})
.Save("report.pdf");
增量风格
不带回调的 AddPage 会直接返回 PdfCanvas。适用于以命令式方式构建内容,例如在循环中。
var doc = PdfDocument.Create().WithTitle("My Report"); foreach (var chapter in chapters) { var canvas = doc.AddPage(PageSizes.A4); canvas.DrawText(chapter.Title, 72, 72, TextStyle.Default.WithSize(18).WithBold()); // ... draw body ... } doc.Save("report.pdf");
文本与 TextStyle
TextStyle 是不可变的。每个 With* 方法都会返回一个新实例——原实例保持不变。先构建一个基础样式,再从中派生变体。
var heading = TextStyle.Default .WithFamily("Helvetica") .WithSize(22) .WithBold() .WithColor(PdfColor.FromHex("#1A56A0")); var body = TextStyle.Default .WithFamily("Times-Roman") .WithSize(11); var mono = TextStyle.Default .WithFamily("Courier") .WithSize(10); canvas.DrawText("Invoice #1024", 72, 72, heading); canvas.DrawText("Due: July 17, 2026", 72, 106, body); // Alignment canvas.DrawText("Right-aligned", 540, 72, body.WithAlignment(TextAlignment.Right)); // Decorations canvas.DrawText("underlined", 72, 140, body.WithUnderline()); canvas.DrawText("strikethrough", 72, 160, body.WithStrikethrough()); canvas.DrawText("overline", 72, 180, body.WithOverline());
TextStyle 参考
| 方法 | 说明 |
|---|---|
| .WithFamily(name) | 按名称指定标准字体。内置字体:Helvetica、Times-Roman、Courier、Symbol、ZapfDingbats。也接受在 FontRegistry 中注册的任意字体族。 |
| .WithFontFile(path) | 通过绝对文件路径嵌入 TrueType / OpenType 字体。优先级高于 WithFamily。 |
| .WithSize(pts) | 字号,单位为 PDF 点。默认值:12。 |
| .WithColor(color) | 文本前景色。默认值:PdfColor.Black。 |
| .WithBold() | 粗体字重。 |
| .WithItalic() | 斜体样式。 |
| .WithAlignment(a) | TextAlignment.Left(默认)、Center、Right。X 为参考点。 |
| .WithUnderline() | 下划线装饰。 |
| .WithStrikethrough() | 删除线装饰。 |
| .WithOverline() | 上划线装饰。 |
| .WithVertical() | 将文本逆时针旋转 90°。 |
| .WithRightToLeft() | 反转码位顺序以实现视觉上的 RTL 渲染(希伯来语、阿拉伯语)。请与 TextAlignment.Right 搭配使用,使 X 成为右侧锚点。 |
PdfColor
// Named colours PdfColor.Black PdfColor.White PdfColor.Red PdfColor.Green PdfColor.Blue PdfColor.Yellow PdfColor.Orange PdfColor.Gray PdfColor.LightGray PdfColor.DarkGray // From CSS hex string PdfColor.FromHex("#1A56A0") // From RGB bytes (0–255) PdfColor.FromRgb(26, 86, 160) // Constructor form — same as FromRgb new PdfColor(26, 86, 160)
测量文本宽度
使用 MeasureTextWidth 可以将标签相对于其他内容定位:
float w = canvas.MeasureTextWidth("Hello", body); canvas.DrawText("World", 72 + w + 4, 72, body);
多行文本换行
DrawTextBox 会将文本自动换行以适应一个边界框。硬换行符(\n)会强制换行;单词永远不会被从中间截断。该方法返回文本溢出边界框的字符位置索引,以便您可以在第二个框中或下一页继续该段落。
var body = TextStyle.Default.WithFamily("LiberationSans").WithSize(11); string text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\n\n" + "Second paragraph starts here."; // Render into a 400 × 120 pt box; returns index of first character that did not fit int overflow = canvas.DrawTextBox(text, x: 72, y: 72, width: 400, height: 120, body); if (overflow < text.Length) { // Continue overflowed text in a second box below canvas.DrawTextBox(text.Substring(overflow), x: 72, y: 210, width: 400, height: 120, body); } // Centre- and right-aligned boxes canvas.DrawTextBox("Centred text\nSecond line", 72, 350, 300, 80, body.WithAlignment(TextAlignment.Center)); canvas.DrawTextBox("Right-aligned\nAll lines snap right", 72, 450, 300, 80, body.WithAlignment(TextAlignment.Right));
DrawTextBox 参考
| 参数 | 说明 |
|---|---|
| text | 要渲染的字符串。\n 会在该位置强制换行。 |
| x, y | 边界框左上角在 PDF 点单位下的坐标。 |
| width, height | 边界框尺寸。超出 y + height 的文本会被省略。 |
| style | TextStyle——字体、字号、颜色和对齐方式均会生效。TextAlignment 是相对于边界框宽度而言的。 |
| 返回值 int | 第一个未能容纳的字符的索引。如果全部文本都被容纳,则等于 text.Length。 |
形状
ShapeStyle 独立控制填充和描边。所有形状方法都接受一个可选的 ShapeStyle;默认值为 1 pt 黑色描边、无填充。
// Rectangles canvas.DrawRectangle(50, 50, 200, 80, ShapeStyle.Filled(PdfColor.LightGray)); canvas.DrawRectangle(50, 150, 200, 80, ShapeStyle.Stroked(PdfColor.Black, width: 2)); canvas.DrawRectangle(50, 250, 200, 80, ShapeStyle.Filled(PdfColor.FromHex("#E8F4FF")) .WithStroke(PdfColor.Blue, width: 1)); canvas.DrawRectangle(50, 350, 200, 80, ShapeStyle.Stroked(PdfColor.DarkGray).Dashed()); // Ellipses (same ShapeStyle options) canvas.DrawEllipse(300, 50, 150, 80, ShapeStyle.Filled(PdfColor.Blue)); // Polygons var triangle = new List<(float x, float y)> { (300, 200), (400, 350), (200, 350) }; canvas.DrawPolygon(triangle, ShapeStyle.Filled(PdfColor.Orange) .WithStroke(PdfColor.DarkGray));
ShapeStyle 参考
| 工厂方法 / 方法 | 说明 |
|---|---|
| ShapeStyle.Filled(color) | 纯色填充,无描边。 |
| ShapeStyle.Stroked(color, width) | 仅描边,无填充。宽度默认为 1。 |
| .WithFill(color) | 添加或替换填充颜色。 |
| .WithStroke(color, width) | 添加或替换描边颜色和宽度。 |
| .WithNoFill() | 移除填充。 |
| .WithNoStroke() | 移除描边。 |
| .Dashed() | 为描边设置虚线样式。 |
| .Dotted() | 为描边设置点线样式。 |
| .WithFillOpacity(alpha) | 填充透明度。1.0 = 不透明(默认);0.0 = 完全透明。参见不透明度。 |
| .WithStrokeOpacity(alpha) | 描边透明度。与 WithFillOpacity 使用相同的取值范围。 |
不透明度与透明度
可以在形状上独立设置填充和描边的透明度,或通过 StrokeStyle 设置整体线条的不透明度。所有不透明度值的范围为 0.0(完全透明)到 1.0(完全不透明,默认值)。
// Fill opacity ramp — five blue squares from opaque to 20 % opacity for (int i = 0; i < 5; i++) canvas.DrawRectangle(72 + i * 50, 100, 40, 40, ShapeStyle.Filled(PdfColor.Blue).WithFillOpacity(1f - i * 0.2f)); // Stroke opacity canvas.DrawRectangle(72, 170, 200, 40, ShapeStyle.Stroked(PdfColor.Red, 3f).WithStrokeOpacity(0.4f)); // Overlapping semi-transparent shapes — backgrounds show through canvas.DrawRectangle(80, 240, 150, 100, ShapeStyle.Filled(PdfColor.Red)); canvas.DrawRectangle(155, 265, 150, 100, ShapeStyle.Filled(PdfColor.Blue).WithFillOpacity(0.5f)); canvas.DrawEllipse(115, 300, 150, 80, ShapeStyle.Filled(PdfColor.Green).WithFillOpacity(0.4f)); // Line opacity via StrokeStyle canvas.DrawLine(72, 400, 450, 400, StrokeStyle.Default.WithWidth(4).WithColor(PdfColor.DarkGray).WithOpacity(0.5f));
不透明度 API 摘要
| 方法 | 说明 |
|---|---|
| ShapeStyle.WithFillOpacity(alpha) | 矩形、椭圆和多边形的填充透明度。 |
| ShapeStyle.WithStrokeOpacity(alpha) | 相同形状类型的描边透明度。 |
| StrokeStyle.WithOpacity(alpha) | 使用 DrawLine 和 DrawCurve 绘制的线条的不透明度。 |
线条与曲线
StrokeStyle 之于线条,正如 ShapeStyle 之于填充形状。
// Straight lines canvas.DrawLine(72, 100, 520, 100); // 1 pt solid black canvas.DrawLine(72, 130, 520, 130, StrokeStyle.Default.WithWidth(2).WithColor(PdfColor.Blue)); canvas.DrawLine(72, 160, 520, 160, StrokeStyle.Default.WithWidth(1).Dashed()); // Smooth curve through control points (Catmull-Rom spline) var pts = new List<(float, float)> { (72, 300), (150, 260), (250, 320), (350, 270), (450, 310), (520, 280) }; canvas.DrawCurve(pts, StrokeStyle.Default.WithWidth(2).WithColor(PdfColor.Red));
图像
DrawImage 接受原始 JPEG 字节,或原始 RGB24 字节(每像素 3 字节,按行主序排列)。图像的左上角会放置在 (x, y) 处,并按指定尺寸缩放。
// JPEG from disk byte[] jpeg = File.ReadAllBytes("logo.jpg"); canvas.DrawImage(jpeg, pixelWidth: 400, pixelHeight: 200, isJpeg: true, x: 72, y: 72, width: 200, height: 100); // Raw RGB24 (e.g. a gradient generated in code) const int W = 200, H = 150; var rgb = new byte[W * H * 3]; for (int row = 0; row < H; row++) for (int col = 0; col < W; col++) { int i = (row * W + col) * 3; rgb[i] = (byte)(col * 255 / W); // R rgb[i + 1] = (byte)(row * 255 / H); // G rgb[i + 2] = 128; // B } canvas.DrawImage(rgb, W, H, isJpeg: false, x: 72, y: 72, width: 200, height: 150);
表格
PdfTable 用于布局一个带有表头行、可选交替行背景、自动单元格文本换行以及可配置边框的网格。将其传递给 canvas.DrawTable() 即可在指定位置渲染它。
// Column widths in PDF points var table = new PdfTable(new float[] { 180, 80, 90, 90 }) .WithHeaderBackground(new PdfColor(26, 86, 160)) .WithAlternateRowBackground(new PdfColor(240, 245, 252)) .WithBorder(new PdfColor(200, 200, 200), 0.5f) .WithCellPadding(5f) .WithCellTextStyle(TextStyle.Default.WithFamily("LiberationSans").WithSize(10)) .WithHeaderTextStyle( TextStyle.Default.WithFamily("LiberationSans").WithSize(10) .WithBold().WithColor(PdfColor.White)); // First AddRow call becomes the header row table.AddRow("Product", "Qty", "Unit Price", "Total"); table.AddRow("PDF Library Pro", "3", "$400.00", "$1 200.00"); table.AddRow("Report Designer", "1", "$250.00", "$250.00"); table.AddRow("Support (12 mo.)", "1", "$500.00", "$500.00"); // tableBottom receives the Y coordinate just below the last rendered row canvas.DrawTable(table, x: 72, y: 72, out float tableBottom); // Border-less "report" style var report = new PdfTable(new float[] { 200, 100, 100 }) .WithNoBorder() .WithHeaderBackground(new PdfColor(245, 245, 245)) .WithCellPadding(4f) .WithCellTextStyle(TextStyle.Default.WithFamily("LiberationSans").WithSize(10)) .WithHeaderTextStyle( TextStyle.Default.WithFamily("LiberationSans").WithSize(10).WithBold()); report.AddRow("Category", "Revenue", "Growth"); report.AddRow("North America", "$1.24M", "+12%"); report.AddRow("Europe", "$0.89M", "+8%"); canvas.DrawTable(report, 72, 72 + tableBottom + 20);
PdfTable 参考
| 方法 | 说明 |
|---|---|
| new PdfTable(widths) | 各列宽度,单位为 PDF 点。元素个数决定列数。 |
| .WithHeaderBackground(color) | 首行(表头行)的背景填充色。 |
| .WithAlternateRowBackground(color) | 每个偶数数据行的背景填充色。 |
| .WithBorder(color, width) | 网格线颜色和宽度。 |
| .WithNoBorder() | 取消所有网格线。 |
| .WithCellPadding(pts) | 应用于每个单元格的内边距(四周)。 |
| .WithCellTextStyle(style) | 数据单元格的默认 TextStyle。 |
| .WithHeaderTextStyle(style) | 表头行的 TextStyle。 |
| .AddRow(col1, col2, …) | 添加一行。每列传入一个字符串。第一次调用生成表头行,后续调用生成数据行。较长的单元格文本会自动换行。 |
| canvas.DrawTable(table, x, y) | 在指定位置渲染表格。 |
| canvas.DrawTable(table, x, y, out float bottom) | 与上面相同,但同时返回最后一行下方的 Y 坐标——便于在表格下方放置内容。 |
链接与工具提示
在任意矩形区域上添加可点击的超链接或悬停工具提示。该区域由左上角 (x, y) 加上 width 和 height 定义。
var linkStyle = TextStyle.Default .WithColor(PdfColor.Blue).WithUnderline(); canvas.DrawText("Visit majorsilence.com", 72, 110, linkStyle); canvas.AddLink(72, 96, width: 220, height: 18, uri: "https://majorsilence.com"); // Tooltip (shows in supporting PDF viewers on hover) canvas.DrawRectangle(72, 140, 200, 40, ShapeStyle.Filled(PdfColor.LightGray).WithStroke(PdfColor.Gray)); canvas.DrawText("Hover for info", 82, 165, TextStyle.Default); canvas.AddTooltip(72, 140, 200, 40, tooltip: "This text appears on hover.");
TrueType 字体
五种内置标准字体(Helvetica、Times-Roman、Courier、Symbol、ZapfDingbats)覆盖 ASCII + Latin-1。若需要 Unicode 文本、表情符号或 CJK,请嵌入一种 TrueType 字体。
通过文件路径嵌入
最简单的方式:让 TextStyle 直接指向一个 .ttf 或 .otf 文件。字体在每个文档中只会被读取一次并被缓存。
var custom = TextStyle.Default .WithFontFile("/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf") .WithSize(14); canvas.DrawText("café résumé naïve", 72, 72, custom); canvas.DrawText("Bold variant", 72, 96, custom.WithBold());
FontRegistry——命名字体族 + 回退链
在一个或多个逻辑名称下注册字体族,定义回退链(当主字体没有相应字形时使用),并将该注册表附加到文档上。画布随后会自动对文本进行分段,将每个字符路由到第一个能够渲染它的字体。
var fonts = new FontRegistry() // Register individual variants .AddFamily("LiberationSans", regular: "Fonts/LiberationSans-Regular.ttf", bold: "Fonts/LiberationSans-Bold.ttf", italic: "Fonts/LiberationSans-Italic.ttf", boldItalic: "Fonts/LiberationSans-BoldItalic.ttf") // Or scan a directory — detects variants from filenames .AddDirectory("Fonts/") // NotoSans fills in any glyph LiberationSans is missing .AddFallback("NotoSans"); PdfDocument.Create() .WithFontRegistry(fonts) .AddPage(PageSizes.A4, canvas => { var style = TextStyle.Default .WithFamily("LiberationSans").WithSize(14); canvas.DrawText("Hello κόσμε мир", 72, 72, style); canvas.DrawText("Bold text", 72, 96, style.WithBold()); canvas.DrawText("Italic text", 72, 118, style.WithItalic()); }) .Save("output.pdf");
AddDirectory 期望文件名符合 FamilyName-Regular.ttf、FamilyName-Bold.ttf 等模式。任何已注册字体都无法渲染的字符会被输出为 .notdef 方框——不会抛出异常。使用前可通过 registry.Contains("FamilyName") 检查某字体族是否已成功加载。
多页文档
按需多次调用 AddPage。页面会按添加顺序写出。
var doc = PdfDocument.Create() .WithTitle("Report") .WithAuthor("Majorsilence") .WithFontRegistry(fonts); string[] chapters = { "Introduction", "Methods", "Results" }; var title = TextStyle.Default.WithSize(24).WithBold(); var footer = TextStyle.Default.WithSize(9).WithColor(PdfColor.Gray); for (int i = 0; i < chapters.Length; i++) { int pageNum = i + 1; doc.AddPage(PageSizes.A4, canvas => { canvas.DrawText(chapters[i], 72, 72, title); canvas.DrawText( $"Page {pageNum} of {chapters.Length}", PageSizes.A4.Width / 2, PageSizes.A4.Height - 30, footer.WithAlignment(TextAlignment.Center)); }); } doc.Save("report.pdf");
PageSizes.Letter.Landscape()(或任意其他尺寸)即可交换宽度和高度。同一文档内的各页可以自由混用不同的方向。
保存选项
// Write to a file doc.Save("report.pdf"); // Write to any Stream (e.g. an ASP.NET response body) doc.Save(Response.Body); // Get bytes — useful for returning from an API endpoint byte[] bytes = doc.ToBytes(); return File(bytes, "application/pdf", "report.pdf");
Task.Run 中。
元数据与 PDF 版本
元数据会出现在阅读器的文档属性中。PDF 版本控制文件头和交叉引用格式。
PdfDocument.Create()
.WithTitle("Invoice #INV-2026-0042")
.WithAuthor("Majorsilence Corp")
.WithSubject("Sales invoice")
.WithCreator("MyApp 1.0")
// PDF 1.4 (default) — broadest reader compatibility
.WithVersion(PdfVersion.Pdf14)
// PDF 2.0 — adds XMP metadata stream + compressed xref
// .WithVersion(PdfVersion.Pdf20)
.AddPage(PageSizes.Letter, canvas => { /* ... */ })
.Save("invoice.pdf");
| 版本 | 说明 |
|---|---|
| PdfVersion.Pdf14 | 默认值。%PDF-1.4 文件头。传统交叉引用表。几乎所有 PDF 阅读器都支持。 |
| PdfVersion.Pdf20 | ISO 32000-2。%PDF-2.0 文件头。附加到文档目录上的 XMP 元数据流。使用压缩交叉引用流而非普通 xref 表。 |
PDF/A 合规
WithConformance 将文档标记为 PDF/A——一种为长期存档而设计的 ISO PDF 子集。该库会自动插入所需的 XMP 元数据流以及一个嵌入的 sRGB ICC 输出意图。所有字体都必须通过 FontRegistry 嵌入;标准 Type 1 字体、加密以及透明度(对于 A-1b 级别)均不被允许。
var fonts = new FontRegistry().AddDirectory("Fonts/").AddFallback("NotoSans"); PdfDocument.Create() .WithConformance(PdfConformance.PdfA2b) // sets PDF 1.7 header automatically .WithFontRegistry(fonts) // embedded fonts are required .WithTitle("Archived Document") .WithAuthor("Majorsilence") .WithSubject("Archival demonstration") .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Archival content", 72, 72, TextStyle.Default.WithFamily("LiberationSans").WithSize(14)); }) .Save("archive.pdf");
PdfConformance 级别
| 级别 | 基于 | 说明 |
|---|---|---|
| PdfConformance.PdfA1b | PDF 1.4 | 最大程度的阅读器兼容性。无透明度。广泛用于存档。 |
| PdfConformance.PdfA2b | PDF 1.7 | 允许透明度。推荐用于新建档案。 |
| PdfConformance.PdfA3b | PDF 1.7 | 与 PDF/A-2b 相同,并额外支持嵌入文件附件。 |
WithConformance 自动插入。可使用 veraPDF 等 PDF/A 校验工具来确认输出的完全合规性。PDF/A 无法与密码保护或公钥加密同时使用。
从右到左文本
在 TextStyle 上调用 .WithRightToLeft() 可反转码位顺序,以实现视觉上的 RTL 渲染。请将其与 TextAlignment.Right 搭配使用,使 X 坐标成为右侧锚点。希伯来语无需整形引擎即可正确渲染;阿拉伯语会显示视觉反转效果(正确的连字需要借助 HarfBuzz 等外部整形引擎)。
using Majorsilence.Pdf; // Build a registry with Hebrew and Arabic script fonts var reg = new FontRegistry() .AddDirectory("Fonts/") // picks up NotoSansHebrew, NotoSansArabic, etc. .AddFallback("NotoSans") .AddFallback("NotoSansHebrew") .AddFallback("NotoSansArabic"); // Check whether the optional script font actually loaded string hebrewFamily = reg.Contains("NotoSansHebrew") ? "NotoSansHebrew" : "NotoSans"; float pageW = PageSizes.A4.Width; float rightEdge = pageW - 72; // right margin anchor var rtl = TextStyle.Default .WithFamily(hebrewFamily) .WithSize(16) .WithRightToLeft() .WithAlignment(TextAlignment.Right); PdfDocument.Create() .WithFontRegistry(reg) .AddPage(PageSizes.A4, canvas => { canvas.DrawText("שלום עולם", rightEdge, 72, rtl.WithSize(24).WithBold()); canvas.DrawText("ספר, ישראל, שלום, אהבה", rightEdge, 110, rtl); // Mix LTR and RTL on the same line var ltr = TextStyle.Default.WithFamily("LiberationSans").WithSize(14); canvas.DrawText("Hello, World!", 72, 160, ltr); canvas.DrawText("!שלום, עולם", rightEdge, 160, rtl.WithSize(14)); }) .Save("rtl.pdf");
NotoSansHebrew 和 NotoSansArabic 已包含在随附的 Majorsilence.Drawing.Common 字体包中,会被 AddDirectory 自动识别。任何已注册字体都无法渲染的字符会显示为 .notdef 方框。
密码保护
对文档进行加密,使阅读者必须提供密码才能打开。您还可以限制阅读者对文档可执行的操作(打印、复制文本等)。
该加密使用标准安全处理程序第 4 版(Standard Security Handler Rev 4)、AES-128-CBC——这是 PDF 1.5–1.7 中定义、并被所有主流阅读器支持的算法。
using Majorsilence.Pdf; using Majorsilence.Pdf.Security; var security = PdfSecurity .Protect(userPassword: "open123", ownerPassword: "owner456") .WithPermissions(PdfPermissions.Print | PdfPermissions.CopyText); PdfDocument.Create() .WithSecurity(security) .WithTitle("Confidential Report") .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Confidential document", 72, 72, TextStyle.Default.WithSize(18).WithBold()); }) .Save("protected.pdf");
PdfSecurity 与 PdfPermissions 参考
| 成员 | 说明 |
|---|---|
| PdfSecurity.Protect(user, owner) | 创建一个安全描述符。user 是打开文件所需的密码;owner 可解锁所有限制。 |
| .WithPermissions(flags) | 要授予的 PdfPermissions 标志位字段。默认:不授予任何权限(只读)。 |
| PdfPermissions.Print | 允许打印。 |
| PdfPermissions.CopyText | 允许选择和复制文本。 |
| doc.WithSecurity(security) | 在保存前将安全描述符附加到文档上。 |
数字签名
使用持有私钥的 X509Certificate2,嵌入一个 PKCS#7 分离式数字签名(adbe.pkcs7.detached,SHA-256)。在生产环境中,请从 X509Store 或由受信任 CA 签发的 .pfx 文件中加载证书。
using Majorsilence.Pdf; using Majorsilence.Pdf.Security; using System.Security.Cryptography.X509Certificates; // Load a certificate with a private key (from file, store, HSM, etc.) X509Certificate2 cert = X509CertificateLoader.LoadPkcs12( File.ReadAllBytes("signer.pfx"), password: "pfxpassword", X509KeyStorageFlags.Exportable); var sigOpts = new PdfSignatureOptions(cert) .WithReason("Document approval") .WithSignerName("Jane Smith") .WithLocation("Toronto, ON"); PdfDocument.Create() .WithSignature(sigOpts) .WithTitle("Signed Report") .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Digitally signed document", 72, 72, TextStyle.Default.WithSize(18).WithBold()); }) .Save("signed.pdf");
PdfSignatureOptions 参考
| 成员 | 说明 |
|---|---|
| new PdfSignatureOptions(cert) | 基于一个具有可导出私钥的 X509Certificate2 创建签名选项。 |
| .WithReason(text) | 写入签名字典的签署原因(会出现在阅读器的签名面板中)。 |
| .WithSignerName(name) | 可读的签署人姓名。 |
| .WithLocation(location) | 签署的物理或逻辑位置。 |
| doc.WithSignature(opts) | 将签名选项附加到文档。签名会在保存时计算并嵌入。 |
可见签名外观
调用 .WithAppearance() 可在页面上渲染一个可见的签名框——适用于需要可见“签署处”字段的文档。该框会显示边框、“Digitally Signed”文字以及签署人姓名。若不调用该方法,签名在密码学上依然存在,但不可见。
var sigOpts = new PdfSignatureOptions(cert) .WithReason("Document approved") .WithSignerName("Jane Smith") .WithLocation("Toronto, ON") .WithAppearance(x: 72, y: 690, width: 220, height: 60); PdfDocument.Create() .WithSignature(sigOpts) .AddPage(PageSizes.A4, canvas => { /* ... content ... */ }) .Save("signed.pdf");
基于证书的加密
加密一个 PDF,使其只能由特定 X.509 证书私钥的持有者打开——无需共享密码。使用 /Filter /Adobe.PubSec,V=4,AES-128。支持多个接收者证书;任何一个持有者都可以打开该文档。
using Majorsilence.Pdf; using Majorsilence.Pdf.Security; using System.Security.Cryptography.X509Certificates; // Load the recipient's public certificate (no private key needed at encryption time) X509Certificate2 cert = new X509Certificate2("recipient.cer"); var security = PdfPublicKeySecurity.ForRecipients(cert) .WithPermissions(PdfPermissions.Print | PdfPermissions.CopyText); PdfDocument.Create() .WithPublicKeySecurity(security) .WithTitle("Confidential Report") .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Only the certificate holder can open this.", 72, 72, TextStyle.Default.WithSize(14)); }) .Save("encrypted.pdf"); // Multiple recipients — any one of them can open the document var multi = PdfPublicKeySecurity.ForRecipients(cert1, cert2, cert3);
PdfPublicKeySecurity 参考
| 成员 | 说明 |
|---|---|
| PdfPublicKeySecurity.ForRecipients(cert, …) | 为一个或多个接收者证书创建一个公钥安全描述符。每个接收者的公钥都会通过 CMS EnvelopedData(RSA)加密一份文档密钥的副本。 |
| .WithPermissions(flags) | 要授予的 PdfPermissions 标志位字段(与密码保护使用相同的标志)。 |
| doc.WithPublicKeySecurity(security) | 将该描述符附加到文档。与 WithSecurity(密码)和 WithConformance(PDF/A)互斥。 |
System.Security.Cryptography.X509Certificates.CertificateRequest 在运行时生成一个自签名证书——具体模式请参见示例项目中的示例 23。
PDF 合并
PdfMerger 将任意数量独立生成的 PDF 字节数组串联成一个文档。每个源 PDF 可以拥有不同的页面尺寸、方向和嵌入字体——这些都会在输出中被保留。
using Majorsilence.Pdf; // Produce two source documents as byte arrays byte[] coverPage = PdfDocument.Create() .WithTitle("Quarterly Report") .AddPage(PageSizes.A4, canvas => { canvas.DrawRectangle(0, 0, PageSizes.A4.Width, PageSizes.A4.Height, ShapeStyle.Filled(new PdfColor(30, 80, 160))); canvas.DrawText("Quarterly Report — Q2 2026", 72, 280, TextStyle.Default.WithSize(36).WithBold().WithColor(PdfColor.White)); }) .ToBytes(); byte[] contentPages = PdfDocument.Create() .AddPage(PageSizes.A4, canvas => { canvas.DrawText("Financial Summary", 72, 72, TextStyle.Default.WithSize(22).WithBold()); canvas.DrawText("Net Income: $1,500,000", 72, 120, TextStyle.Default.WithSize(12)); }) .ToBytes(); // Merge into a single document byte[] merged = new PdfMerger() .Add(coverPage) .Add(contentPages) .WithTitle("Quarterly Report Q2 2026") .WithAuthor("Example Corp") .WithSubject("Financial Summary") .WithCreator("MyApp 2.0") .Merge(); File.WriteAllBytes("merged.pdf", merged);
PdfMerger 参考
| 方法 | 说明 |
|---|---|
| new PdfMerger() | 创建一个新的合并器。无需任何参数。 |
| .Add(pdfBytes) | 将一个 PDF(以 byte[] 形式)的页面追加到合并队列中。可根据需要多次调用;页面按顺序输出。 |
| .WithTitle(text) | 合并后文档元数据中的标题。 |
| .WithAuthor(text) | 合并后文档元数据中的作者。 |
| .WithSubject(text) | 合并后文档元数据中的主题。 |
| .WithCreator(text) | 合并后文档元数据中的创建应用程序字符串。 |
| .Merge() | 执行合并并以 byte[] 形式返回合并后的 PDF。输出始终为 PDF 1.4。 |
PdfMerger 的输出始终为 PDF 1.4。